Last Updated: March 28, 2026
We have a string and a dictionary. The question is whether we can break the string into pieces where every piece is a word in the dictionary. We can reuse words as many times as we want, and we need to cover the entire string with no characters left over.
This is fundamentally a decision problem: can we find at least one valid segmentation? We don't need to return the actual segmentation, just true or false.
The key observation is that this has overlapping subproblems. If we know whether the substring from index i to the end can be segmented, we can reuse that answer when checking different ways to reach index i. That's a strong hint toward dynamic programming.
1 <= s.length <= 300 -- With n up to 300, O(n^2) gives us 90,000 operations, and O(n^3) gives 27 million. Both are comfortable. But brute force recursion without memoization is O(2^n), which would be far too slow.1 <= wordDict.length <= 1000 -- The dictionary can be large, so converting it to a hash set for O(1) lookups is important.1 <= wordDict[i].length <= 20 -- Word lengths are short. We only need to check substrings of length up to 20 at each position, which can optimize the inner loop.The most natural way to think about this: start at the beginning of the string and try every possible first word. If the substring s[0..k] matches a dictionary word, then recursively check whether the remaining string s[k+1..end] can also be segmented. If any branch succeeds, return true. If no branch works, return false.
This is essentially exploring a decision tree. At each position in the string, we branch into all dictionary words that match starting at that position. We're looking for any path through this tree that consumes the entire string.
wordDict to a hash set for O(1) lookups.canBreak(start) that returns true if s[start..end] can be segmented.start == s.length(), we've consumed the entire string, so return true.start + 1 to s.length(), check if the substring s[start..end-1] is in the word set.canBreak(end). If that returns true, return true.The same suffix gets checked multiple times through different segmentation paths. What if we stored the result for each starting index the first time we computed it?
The brute force recursion does a lot of duplicate work. The function canBreak(start) only depends on the starting index, and there are only n possible starting indices (0 through n-1). So if we cache the result of canBreak(start) the first time we compute it, every subsequent call with the same start returns immediately.
This is the classic memoization pattern. Same recursive logic, but with a memo table that stores whether each suffix can be segmented. The recursion tree collapses from exponential to linear in the number of unique subproblems.
wordDict to a hash set for O(1) lookups.n + 1, initialized to "unvisited" (use -1 or null to distinguish from true/false).canBreak(start) the same as before, but before computing, check the memo. If we've already solved this subproblem, return the cached result.The recursion adds overhead from function calls and stack frames. What if we computed the subproblems in a natural bottom-up order, eliminating recursion entirely?
Instead of recursion, we can fill a DP table iteratively. Define dp[i] as "can the first i characters of s be segmented using the dictionary?" The answer we want is dp[n], where n is the length of s.
The recurrence is straightforward: dp[i] is true if there exists some j < i such that dp[j] is true AND the substring s[j..i-1] is in the dictionary. In other words, we can form the first i characters if we can form the first j characters AND the remaining chunk from j to i is a valid word.
The bottom-up DP builds the solution incrementally. dp[0] = true is our anchor: the empty prefix is always valid. Then for each position i, we check all possible "last words" that could end at position i. If any of those last words starts at a position j where dp[j] is already true, then we can form the first i characters by extending a valid segmentation of the first j characters with the word s[j..i-1].
The break statement is important for efficiency: once we find one valid split point j for position i, we don't need to check other values of j. We only need to know that at least one valid segmentation exists.
wordDict to a hash set for O(1) lookups.dp of size n + 1, initialized to false.dp[0] = true (empty string can always be segmented).i from 1 to n:j from 0 to i - 1:dp[j] is true and s[j..i-1] is in the word set, set dp[i] = true and break.dp[n].