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 a decision problem: can we find at least one valid segmentation? We don't need to return the actual segmentation, only true or false.
The problem has overlapping subproblems. Whether the substring from index i to the end can be segmented does not depend on how we reached i, so that answer can be reused across every segmentation path that lands at i. That repetition is what dynamic programming eliminates.
1 <= s.length <= 300: With n up to 300, O(n^3) is about 27 million operations, which is fine. Plain recursion without memoization is exponential and times out, so caching subproblem results is required.1 <= wordDict.length <= 1000: The dictionary can be large, so converting it to a hash set gives O(1) average-case word lookups instead of scanning the list.1 <= wordDict[i].length <= 20: Words are at most 20 characters. At each position we only need to test candidate words up to length 20, which bounds the inner work regardless of n.Start at the beginning of the string and try every possible first word. If the prefix s[0..k] matches a dictionary word, 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 explores a decision tree. At each position in the string, we branch into all dictionary words that match starting at that position, and we want any path through the 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 recomputed many times through different segmentation paths. Storing the result for each starting index the first time it is computed removes that redundancy.
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.
The logic is identical to the brute force, with a memo table that stores whether each suffix can be segmented. The number of distinct subproblems drops to n, so the work that was exponential becomes polynomial.
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. The same subproblems can be computed in bottom-up order with a loop, which removes the 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.
When the loop reaches position i, every dp[j] for j < i is already final, because the outer loop fills positions in increasing order. So the test dp[j] && s[j..i-1] in dict correctly decides dp[i] using only settled subproblems.
The break is safe because dp[i] is a boolean: one valid split point j is enough to prove the first i characters are segmentable, and finding more split points would not change the answer.
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].The hash-set DP rebuilds a substring for every (i, j) pair, and each rebuild copies up to n characters. A trie lets us test all candidate words starting at a position by walking the string one character at a time, with no substring allocation.
A trie stores the dictionary so that shared prefixes share nodes. Starting from any index i, we can follow s[i], s[i+1], ... down the trie one character at a time. If the walk reaches a word-end node after consuming the character at index k, then s[i..k] is a dictionary word, so if dp[i] is true we set dp[k + 1] to true. We never build a substring or hash one; each step is a single child lookup.
This keeps the same dp[i] meaning as the bottom-up DP (can the first i characters be segmented), but replaces the inner hash-set loop, which does O(n) substring work per check, with a walk down the trie that stops as soon as the path runs out of matching nodes. The walk from any index cannot go further than the longest dictionary word, so the inner work is bounded by L, the maximum word length.
wordDict into a trie. Each node has up to 26 children and a flag marking whether a word ends there.dp of size n + 1, with dp[0] = true.i from 0 to n - 1, if dp[i] is false, skip it (no segmentation reaches i).s[i], s[i+1], .... At each step to index k, if the current node marks a word end, set dp[k + 1] = true. Stop the walk when there is no matching child.dp[n].