We're given a string and a dictionary, and we need to find every way to split the string into a sequence of dictionary words. The related Word Break problem (LeetCode 139) asks only "can it be segmented?" and a boolean answer suffices. This problem asks for every valid segmentation, so we have to reconstruct the actual splits rather than just decide feasibility.
That difference matters. A boolean DP table records whether a suffix is segmentable, but it throws away how. To list every sentence, we need to enumerate all the paths through the string, which puts us in backtracking territory.
Three properties shape the approach. The same dictionary word can appear multiple times in one sentence. We need all valid sentences, not just one. And the string length is at most 20, so the number of valid sentences can itself be exponential, which means no algorithm can avoid producing exponential output in the worst case.
Framed differently, this is a search problem over the string. Each split corresponds to a path from the start to the end, where every step consumes a dictionary word that matches the next chunk of characters.
s.length <= 20 → With n at most 20, an exponential search over split points is feasible. Exhaustive backtracking is acceptable.wordDict.length <= 1000 → The dictionary can be large, but a HashSet gives O(1) membership checks, so dictionary size is not a bottleneck.wordDict[i].length <= 10 → Words are short, so at any position we only need to check substrings up to length 10. The brute-force code below checks substrings up to the full remaining length, which still works because non-matching substrings are simply skipped.Try every possible split. Start from position 0. At each position, try every prefix that matches a dictionary word. When one matches, add that word to the current sentence and continue from the position right after it. When the position reaches the end of the string, the current sentence is complete and valid.
This is backtracking. We build sentences word by word, and when no dictionary word matches the remaining string, we undo the last word and try a different one.
This terminates quickly because s.length is at most 20. The number of ways to split a 20-character string is bounded by 2^19, since each of the 19 gaps between characters is either a split point or not. The dictionary constraint rules out most of these splits, so the search visits far fewer paths in practice.
Loading animation...
The brute force explores every prefix at every position, including positions whose remaining suffix cannot be decomposed at all. The next approach computes which positions can reach the end before searching, then skips the rest.
The brute force spends time exploring paths that lead nowhere. The fix is to run a DP pass before backtracking. This pass computes, for each position, whether the suffix starting there can be segmented into dictionary words all the way to the end. It is the same recurrence used to decide feasibility in the basic Word Break problem. During backtracking, we then recurse only into positions whose suffix is known to be segmentable.
The DP array acts as a reachability map. A position marked false can never complete a valid sentence, so the search never enters it. This does not change the worst-case complexity, since the number of valid sentences can still be exponential, but it removes all the wasted work on suffixes that have no segmentation. On inputs like a long run of a characters with a dictionary that fails near the end, the brute force explores an exponential number of dead-end paths while the pruned version rejects the input after one DP pass.
Skipping a position can only be wrong if that position could have led to a valid sentence we then miss. The DP array rules that out: dp[end] is true exactly when the suffix s[end:] has at least one segmentation. So if dp[end] is false, no completion exists from end, and pruning that branch removes only dead ends, never a real answer.
dp where dp[i] is true if the substring s[i:] can be segmented into dictionary words.dp[n] = true (empty suffix is trivially valid).i from n-1 down to 0, check all dictionary words. If s[i:i+len] matches a word and dp[i+len] is true, set dp[i] = true.dp[0] is false, return an empty list immediately.end if dp[end] is true.Loading animation...
The DP pruning eliminates dead-end branches, but when multiple paths converge at the same index, the search still recomputes the sentences for that index every time it arrives. The next approach caches the list of sentences for each starting index so each is computed once.
The subproblems overlap. The set of all valid sentences from index i to the end depends only on i, not on the words chosen before reaching i. Whether the search arrived at index 3 via "cat" or via some other prefix, the sentences for the suffix s[3:] are identical.
So instead of backtracking, which re-explores those overlapping subproblems, we memoize. For each index i, compute the list of all valid sentences starting at i once, then store it. A later visit to the same index returns the cached list directly.
This turns "explore all paths" into "solve each suffix once." There are O(n) distinct starting indices, and each tries every dictionary word that fits in O(m * L) time. Assembling the final sentences still costs time proportional to the total output, which is unavoidable when the output itself can be exponential.
The cached value for index i is the complete set of sentences for the suffix s[i:], and that set never changes once computed because it does not reference any character before i. The difference from Approach 2 is what gets reused. Approach 2 reuses only the boolean "can this suffix be segmented" and still rebuilds every sentence on each visit. This approach reuses the sentences themselves, so a suffix reached from k different prefixes is built once instead of k times.
dfs(start) that returns all valid sentences starting from index start.start == n, return a list containing an empty string (we've segmented the entire string).start is in the memo, return the cached result.end from start+1 to n, check if s[start:end] is in the dictionary.end, and prepend the current word to each.start.Loading animation...