LeetCode 139. Word Break Solved (Dynamic Programming)

This problem is a pretty standard dynamic programming problem, where you start with a recursive/backtracking solution, memoize it and finally understand the pattern to come up with a bottom-up iterative approach. A challenge I face while solving this problem was understanding why memoization was needed because the given examples didn’t seem to need it. But, once I realized that each recursive call took n^2 time complexity it made sense because dynamic programming problems I solved before usually had constant time operations for each recursive call.

So, the approach I came up with was to go through each index of the string and check if the substring from the start to the index was in the wordDict, and then if it was we recurse on the other half. This approach is O(n^3+m) time and O(n) space, where n is the length of the string and m is the length of the wordDict. The other approach is to check if any word in the wordDict matches the first portion of the string and if it did we recurse the other half. The time complexity for this is O(n^2*m) and space is O(n).

The First Approach (my approach)

Recursive Code

class Solution:
    def wordBreak(self, s: str, wordDict: List[str]) -> bool:
        wordDict = set(wordDict)
        memo = {"":True}
        def canBreak(s):
            if s not in memo:
                memo[s] = False
                for i in range(len(s)):
                    if s[i:] in wordDict and canBreak(s[:i]):
                        memo[s] = True
                        break
            return memo[s]
        return canBreak(s)

Iterative Code

class Solution:
    def wordBreak(self, s: str, wordDict: List[str]) -> bool:
        wordDict = set(wordDict)
        dp = [False] * (len(s) + 1)
        dp[-1] = True
        for idx in range(len(s)-1,-1,-1):
            for i in range(idx+1, len(s)+1):
                if s[idx:i] in wordDict and dp[i]:
                    dp[idx] = True
                    break
        return dp[0]

The Second Approach

Recursive Code

class Solution:
    def wordBreak(self, s: str, wordDict: List[str]) -> bool:
        memo = {"":True}
        def canBreak(s):
            if s not in memo:
                memo[s] = False
                for word in wordDict:
                    if s[:len(word)] == word and canBreak(s[len(word):]):
                        memo[s] = True
                        break
            return memo[s]
        return canBreak(s)

Iterative Code

class Solution:
    def wordBreak(self, s: str, wordDict: List[str]) -> bool:
        dp = [False] * (len(s)+1)
        dp[-1] = True
        for idx in reversed(range(len(s))):
            for word in wordDict:
                if s[idx:len(word)+idx] == word and dp[len(word)+idx]:
                    dp[idx] = True
                    break
        return dp[0]