AlgoMaster Logo

Word Break

mediumFrequency9 min readUpdated June 23, 2026

Understanding the Problem

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.

Key Constraints:

  • 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.

Approach 1: Brute Force (Recursion)

Intuition

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.

Algorithm

  1. Convert wordDict to a hash set for O(1) lookups.
  2. Define a recursive function canBreak(start) that returns true if s[start..end] can be segmented.
  3. Base case: if start == s.length(), we've consumed the entire string, so return true.
  4. For each end index from start + 1 to s.length(), check if the substring s[start..end-1] is in the word set.
  5. If it is, recursively call canBreak(end). If that returns true, return true.
  6. If no substring works, return false.

Example Walkthrough

1Start: canBreak(0), try words from index 0
0
l
start=0
1
e
2
e
3
t
4
c
5
o
6
d
7
e
1/4

Code

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.

Approach 2: Top-Down DP (Recursion + Memoization)

Intuition

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.

Algorithm

  1. Convert wordDict to a hash set for O(1) lookups.
  2. Create a memo array of size n + 1, initialized to "unvisited" (use -1 or null to distinguish from true/false).
  3. Define canBreak(start) the same as before, but before computing, check the memo. If we've already solved this subproblem, return the cached result.
  4. After computing, store the result in the memo before returning.

Example Walkthrough

1Start: canBreak(0), try words starting at index 0
0
c
start=0
1
a
2
t
3
s
4
a
5
n
6
d
7
o
8
g
1/7

Code

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.

Approach 3: Bottom-Up DP

Intuition

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.

Algorithm

  1. Convert wordDict to a hash set for O(1) lookups.
  2. Create a boolean array dp of size n + 1, initialized to false.
  3. Set dp[0] = true (empty string can always be segmented).
  4. For each position i from 1 to n:
    • For each position j from 0 to i - 1:
      • If dp[j] is true and s[j..i-1] is in the word set, set dp[i] = true and break.
  5. Return dp[n].

Example Walkthrough

1Base case: dp[0]=true (empty string)
0
true
base
1
false
2
false
3
false
4
false
5
false
6
false
7
false
8
false
9
false
10
false
11
false
12
false
13
false
1/5

Code

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.

Approach 4: Trie + DP

Intuition

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.

Algorithm

  1. Insert every word of wordDict into a trie. Each node has up to 26 children and a flag marking whether a word ends there.
  2. Create a boolean array dp of size n + 1, with dp[0] = true.
  3. For each position i from 0 to n - 1, if dp[i] is false, skip it (no segmentation reaches i).
  4. Otherwise, walk the trie from the root following 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.
  5. Return dp[n].

Example Walkthrough

1Base case: dp[0]=true (empty prefix)
0
true
base
1
false
2
false
3
false
4
false
5
false
6
false
7
false
8
false
1/6

Code