Let's solve the Word Break problem using the Dynamic Programming pattern.
Statement
Given a string, s
, and a dictionary of strings, wordDict
, check if s
can be segmented into a space-separated sequence of one or more dictionary words. If yes, return TRUE; else, return FALSE.
Note: The same word in the dictionary may be used multiple times.
Constraints:
-
s.length
-
wordDict.length
-
wordDict[i].length
-
s
andwordDict[i]
consist of only lowercase English letters. -
All the strings of
wordDict
are unique.
Solution
So far, you’ve probably brainstormed some approaches and have an idea of how to solve this problem. Let’s explore some of these approaches and figure out which one to follow based on considerations such as time complexity and any implementation constraints.
Naive approach
The naive approach to solve this problem is to use a basic recursive strategy, which considers all possible prefixes of a string and checks if they are present in the dictionary. If a prefix is contained in the dictionary, the rest of the string is checked in the same manner.
Although this approach can get us the desired output, it has a time complexity of , where is the length of the string. This is because at each step, we split the string in two, check the prefix in the dictionary, and repeat the process with the rest of the string. The space complexity of this approach is because the depth of the recursion tree can go up to .
Optimized approach using dynamic programming
In the recursive approach, we notice that many prefixes are computed again even though they were checked in the earlier computations. For example, a prefix of two characters contains a prefix of one character that has already been checked. These redundant computations consume a lot of time that can be avoided using dynamic programming. The idea is to use a lookup table, dp
, of size , where is the length of the input string and is added to cater the empty string. This table will store the results of the shorter prefixes that can be used, in lookup time, for solving the longer prefixes.
The dp
is initialized with FALSE except for dp[0]
, which is set TRUE because an empty string is assumed to be a valid word in the dictionary. Then, using two pointers, i
and j
, we check every possible prefix s[j..i]
and whether they’re contained in the dictionary. If the substring s[j..i]
is found in the dictionary, we don’t check further smaller substrings. We also check whether dp[j]
is TRUE, which tells us that the prefix s[0..j]
was found in the dictionary. If both conditions are fulfilled, the corresponding dp
index, i.e., dp[i]
, is set to TRUE. We continue this process until the whole string has been processed. Finally, we return the value of dp[n]
, which tells us that the input string could be segmented into a space-separated sequence of dictionary words.
Let’s look at the following illustration to get a better understanding of the solution: