Word Break (Leetcode #139)
The Word Break problem is a classic challenge in dynamic programming and string manipulation that comes up frequently during technical interviews. It tests your ability to segment strings effectively using a given set of words. In this blog, we will dive into solving the Word Break problem, covering a brute force approach, an efficient solution, and analyzing their respective complexities.
Understanding the Problem Statement
The Word Break problem asks if a given string (s
) can be segmented into space-separated sequences of one or more dictionary words. Given an input string and a dictionary of words, the goal is to determine if the entire string can be split such that every segment is present in the dictionary.
For example:
Input:
s = "leetcode"
wordDict = ["leet", "code"]
Output:
true
Explanation:
"leetcode"
can be segmented as"leet code"
which are both words present in the dictionary.
The problem requires checking all possible segmentations of the string, making it an interesting problem for exploring both brute force and optimized techniques.
Brute Force Approach
A common approach to solve this problem is through recursion. In the brute force solution, we attempt to partition the string in all possible ways and check if each substring exists in the dictionary. Here is a high-level overview of how a brute force solution would work:
Start with the first character of the string.
Incrementally form substrings and check if each substring exists in the given word dictionary.
Recur for the remaining part of the string if a valid segment is found.
This approach is straightforward but highly inefficient due to repeated calculations and overlapping subproblems. As the length of the string increases, the time complexity becomes exponential, which is impractical for large inputs.
Hint to Solve the Problem Efficiently
To solve the problem efficiently, we can use dynamic programming to avoid redundant computations. The hint is to use a boolean array where each element represents whether the substring up to that index can be segmented using words from the dictionary.
Efficient Solution
Below is an efficient solution based on the provided code, which uses dynamic programming to solve the Word Break problem:
class Solution:
def wordBreak(self, s: str, wordDict: List[str]) -> bool:
wordSet = set(wordDict)
res = [False] * (len(s) + 1)
res[0] = True
# Iterate through all positions in the string
for i in range(len(s) + 1):
# Check all words in the wordSet
for word in wordSet:
if (res[i] and (i + len(word)) <= len(s) and s[i : i + len(word)] in wordSet):
res[i + len(word)] = True
return res[-1]
Explanation:
The solution uses a dynamic programming array (
res
) of lengthlen(s) + 1
, whereres[i]
isTrue
if the substrings[:i]
can be segmented using the words in the dictionary.We initialize
res[0]
toTrue
because an empty string can always be segmented.We iterate through the string, and for each position, we check all words in the dictionary to see if the substring from the current position matches any word and can form a valid segment.
If a match is found, we update the dynamic programming array to reflect that the substring can be segmented.
The result is returned by checking
res[len(s)]
, which indicates if the entire string can be segmented.
Time and Space Complexity
Time Complexity: The time complexity is O(n * m), where
n
is the length of the strings
andm
is the total number of words in the dictionary. For each position in the string, we iterate over all words, and the substring check takes constant time on average due to the use of a hash set.Space Complexity: The space complexity is O(n), where
n
is the length of the strings
. We use a dynamic programming array of sizen + 1
to store results for each position in the string.
Conclusion
The Word Break problem is a great example of how dynamic programming can be applied to improve efficiency over brute force approaches. By using a DP array and leveraging the set data structure for fast lookups, we can significantly reduce the time required to solve the problem. Understanding these approaches not only helps in solving similar problems in interviews but also strengthens core algorithmic concepts.
Feel free to try different test cases on your own and see how the optimized solution outperforms the naive one. Happy coding!