class Solution:
    def wordBreak(self, s: str, wordDict: List[str]) -> bool:
        """
        main idea: Bottom up -  put l, le, lee, leet into cache, and look up the dictionary to find if any word match 
        dp[i] for substring s from idex 0 to index i-1, there is a word in wordDict
        tc O(N*len(wordDict)*W) sc O(N)  
        """
        n = len(s)
        dp = [False] * (n+1)
        dp[0] = True
        for i in range(1,n+1):
            for j in range(i):
                if dp[j] != False and s[j:i] in wordDict:
                    dp[i] = True
                    break
        return dp[n]

for this kind of problem, which time complexity is [length of s][size of dict][avg length of words in dict]. We can usually remove [size of dict] by using Tire, remove [avg length of words in dict] by KMP, and what’s more, remove both [size of dict] and [avg length of words in dict] by AC-automata. And of course these are all doable for this problem.

optimization time complexity: [length of s][size of dict][avg length of words in dict]

class Solution:
    def wordBreak(self, s: str, wordDict: List[str]) -> bool:
        
        #tc O(N*len(wordDict)*W) sc O(N)  
        n = len(s)
        wordDict = set(wordDict)
        dp = [False] * (n+1)
        dp[0] = True 
        for i in range(1,n+1):
            for w in wordDict:
                w_len = len(w)
                if w_len <= i and dp[i-w_len] and s[i-w_len:i] == w:
                    dp[i] = True
                    break
        return dp[n]

Top Down solution

class Solution:
    def wordBreak(self, s: str, wordDict: List[str]) -> bool:
        memo = {}
        word_set = set(wordDict)
        return self.helper(s,0,word_set,memo)
    
    def helper(self, s, start, word_set, memo):
        if start in memo:
            return memo[start]
        if start == len(s):
            return True 
        for end in range(start+1, len(s)+1):
            if s[start:end] in word_set and self.helper(s,end,word_set, memo):
                memo[start] = True
                return memo[start]
        memo[start] = False 
        return memo[start]

BFS solution

class Solution:
    def wordBreak(self, s: str, wordDict: List[str]) -> bool:
        """ tc O(N^3) sc O(N)
        """
        word_set = set(wordDict)
        start = 0
        n = len(s)
        seen = set()
        q = collections.deque([start])
        while q:
            cur = q.popleft()
            if cur in seen:
                continue 
            for end in range(cur+1,n+1):
                if s[cur:end] in word_set:
                    q.append(end)
                    if end == n:
                        return True 
                seen.add(cur)
        return False