AlgoMaster Logo

Word Break

Last Updated: March 28, 2026

medium
4 min read

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

Key Constraints:

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

Approach 1: Brute Force (Recursion)

Intuition

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.

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 checked multiple times through different segmentation paths. What if we stored the result for each starting index the first time we computed it?

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.

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.

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. What if we computed the subproblems in a natural bottom-up order, eliminating 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