Word Break

The problem description can be found in LeetCode.

Approach

A brute force approach for this problem would consist of consuming the string s by starting from any word of wordDict. Of course, this would require common characters between the strings. In fact, we would not complete any search with an input like:

s = "hello"
wordDict = ["foo", "bar"]

This would suggest looking any character for the strings in wordDict and start to match them with the characters in s. If we get a complete match for a certain string w in wordDict, we can start to look at the wordDict again with s[:len(w)].

This is a time consuming approach because it would require to go through all the characters in s and then all the characters of each string of wordDict.

However, we should already know about a data structure that can quickly look at prefixes: the Trie. So, we could store all the words in wordDict in a Trie and just iterate over the strings s to look for common characters.

Solution

from typing import Dict, List, Optional


class TrieNode:
    def __init__(self, is_word: Optional[bool] = False) -> None:
        self.children: Dict[str, "TrieNode"] = {}
        self.is_word = is_word


class Trie:
    def __init__(self) -> None:
        self.head = TrieNode()

    def insert(self, word: str) -> None:
        p = self.head

        for char in word:
            if char not in p.children:
                p.children[char] = TrieNode()

            p = p.children[char]

        p.is_word = True

    def search(self, word: str) -> bool:
        return self._contains(word=word, match_word=True)

    def startsWith(self, prefix: str) -> bool:
        return self._contains(word=prefix, match_word=False)

    def _contains(self, word: str, match_word: bool) -> bool:
        p = self.head

        for char in word:
            if char not in p.children:
                return False

            p = p.children[char]

        return p.is_word if match_word else True


class Solution:
    def wordBreak(self, s: str, wordDict: List[str]) -> bool:
        def dfs(i: int, trie: Trie, memo: Dict[int, bool]) -> bool:
            if i in memo:
                return memo[i]
            if i == len(s):
                return True
            if i > len(s):
                return False

            p = trie.head
            for j in range(i, len(s)):
                char = s[j]
                if char not in p.children:
                    memo[i] = False
                    return memo[i]

                p = p.children[char]
                if p.is_word and dfs(j + 1, trie, memo):
                    memo[i] = True
                    return memo[i]

            memo[i] = False
            return memo[i]

        trie = Trie()
        for word in wordDict:
            trie.insert(word)

        return dfs(0, trie, {})

Complexity

  • Time:
  • Space:

Where:

  • is the length of the list wordList.
  • is the length of the longest string in wordList.
  • is the length of the string s.