Word Break
Table of Contents + −
Word Break is a popular dynamic programming question. Dynamic programming is a way to solve a big problem by breaking it into small pieces, solving each piece once, and saving the answer. This one feels like a word puzzle. But it hides the same repeated work that all dynamic programming problems have. So it tests if you can find that repeat and remove it.
🎯 The Problem
You get a string and a list of allowed words, called a dictionary. Here are the rules.
- You must cut the string into pieces.
- Every piece must be a word in the dictionary.
- You can reuse a word as many times as you like.
- You return a true or false answer. No need to show the pieces.
For the string "leetcode" and the dictionary ["leet", "code"], you can cut it as leet plus code. Both are in the dictionary. So the answer is true.
Input: s = "leetcode", wordDict = ["leet", "code"]Output: true
Explanation: "leetcode" can be split into "leet" and "code", both in the dictionary.This diagram shows the cut. We split the string at one spot and check both pieces.
🐢 Approach 1: Plain Recursion (Brute Force)
The idea in one line: try every cut, and if any chain of cuts uses the whole string, answer true.
The idea:
- Start at the front of the string.
- Try every possible first word.
- If the front part is a dictionary word, solve the rest the same way.
How it works:
- Write a function that, from a start position, returns whether the rest can be broken.
- It tries each end point. If the piece from start to that end is a word, it recurses on the remaining part.
- If the start reaches the end of the string, the whole string is used up, so return true.
Why it is weak:
- The same start position gets checked again and again from different paths.
- These repeated checks are overlapping subproblems, the same small case appearing many times.
- The branching grows fast, so it is about O(2ⁿ). Too slow.
Here is the plain recursion code:
def word_break(s, word_dict): words = set(word_dict)
def dfs(index): if index == len(s): return True for end in range(index + 1, len(s) + 1): if s[index:end] in words and dfs(end): return True return False
return dfs(0)⚡ Approach 2: Memoization (Better)
The idea in one line: save each position’s answer so you never solve it twice.
The idea:
- Keep an array
memowherememo[i]says whether the string starting atican be broken.
How it works:
- The first time we solve a position, we store true or false.
- The next time, we read it back instead of recomputing.
- This is memoization, remembering a function result so you never compute it twice.
Why it is faster:
- Each position is solved once.
- The time drops to about O(n²).
Here is the memoized recursion:
from functools import lru_cache
def word_break(s, word_dict): words = set(word_dict)
@lru_cache(None) def dfs(index): if index == len(s): return True return any(s[index:end] in words and dfs(end) for end in range(index + 1, len(s) + 1))
return dfs(0)⚡ Approach 3: Bottom-Up Tabulation (Better)
The idea in one line: build the answer from the smallest prefixes up to the full string.
The idea:
- Tabulation fills a table from the smallest cases toward the answer.
- Make a boolean array
dpof sizen+1. dp[i]is true if the firsticharacters can be fully broken into dictionary words.
How it works:
- Set
dp[0]to true. An empty string needs no words. - For each end point
i, look at every split pointjbefore it. - If
dp[j]is true and the piece fromjtoiis in the dictionary, thendp[i]is true. - The answer is
dp[n].
Why it is solid:
- No recursion stack.
- Each end point is filled once.
This diagram shows the dp table filling for "leetcode". The true cells chain together to the end.
🚀 Approach 4: Tabulation With a Word Set (Best)
The idea in one line: keep the tabulation, but check membership in a set so each lookup is fast.
The idea:
- The plain dictionary is a list, and checking a list is slow.
- Put the words into a set, a structure that checks membership almost instantly.
How it works:
- Build the same
dptable as before. - Each “is this piece a word?” check hits the set, not a list scan.
Why it is best:
- The two nested position loops still make it O(n²).
- But each lookup is now quick instead of a slow scan.
- This is the version you write in the interview.
Steps to Solve
- Put every dictionary word into a set for fast lookups.
- Make a boolean array
dpof sizen+1, all false, then setdp[0]to true. - Walk the end point
ifrom1ton. - For each
i, walk a split pointjfrom0up toi. - If
dp[j]is true and the piece fromjtoiis in the set, setdp[i]true and stop the inner loop. - The answer is
dp[n].
This Python version puts the words in a set, which gives almost instant lookups.
def word_break(s, word_dict): words = set(word_dict) # fast lookups n = len(s) dp = [False] * (n + 1) dp[0] = True # empty prefix is breakable for i in range(1, n + 1): for j in range(i): if dp[j] and s[j:i] in words: dp[i] = True # piece j..i is a word break return dp[n]
s = "leetcode"word_dict = ["leet", "code"]print(word_break(s, word_dict))The output of the above code will be:
TrueLet us walk through the Python version line by line and see why each piece is there.
def word_break(s, word_dict): words = set(word_dict) n = len(s) dp = [False] * (n + 1) dp[0] = True for i in range(1, n + 1): for j in range(i): if dp[j] and s[j:i] in words: dp[i] = True break return dp[n]The line words = set(word_dict) turns the list into a set. A set checks if a word is inside it almost instantly. A plain list would scan every word, which is slow.
The line dp = [False] * (n + 1) makes our table. Here dp[i] is true if the first i characters can be fully broken into dictionary words. We need n + 1 slots so we have one for the empty prefix and one for each character. The line dp[0] = True sets the base. An empty string needs no words, so it is always breakable.
The outer loop for i in range(1, n + 1) picks the end of the prefix we are checking. The inner loop for j in range(i) picks a split point inside that prefix. The check if dp[j] and s[j:i] in words is the heart of it. It asks two things. Is the part before j already breakable. And is the piece from j to i a dictionary word. If both are true, then the whole prefix up to i is breakable, so we set dp[i] = True.
The break stops the inner loop early once we found one good split. We do not need more. The final dp[n] is the answer for the whole string. Trace "leetcode". We get dp[0] true. Then dp[4] true because leet is a word and dp[0] is true. Then dp[8] true because code is a word and dp[4] is true. So the answer is true.
⏱️ Time and Space Complexity
Plain recursion repeats work, so it is O(2ⁿ). Memoization and tabulation each have two nested position loops, so they are O(n²) on the positions, times the cost of slicing and checking a piece. The space is O(n) for the dp table plus the word set. So the optimized tabulation lands at O(n²) time in the usual analysis.
| Approach | Time Complexity | Space Complexity |
|---|---|---|
| Brute force recursion | O(2ⁿ) | O(n) |
| Memoization (top-down) | O(n²) | O(n) |
| Tabulation (bottom-up) | O(n²) | O(n) |
| Tabulation with word set | O(n²) | O(n) |
Tip
The one upgrade interviewers want to hear is the set. A plain list scans every word for each check. A set checks in almost no time. Put the words in a set first and say why. That small move keeps the inner check fast.
🧩 Key Takeaways
- ✅ The question is a yes or no. Can the string be cut into pieces that are all dictionary words.
- ✅
dp[i]is true if the firsticharacters can be fully broken. Start withdp[0]true for the empty string. - ✅ For each end point, look at every split point before it. Both halves must work for the prefix to work.
- ✅ Plain recursion repeats overlapping subproblems, so it is O(2ⁿ). Saving answers brings it to O(n²).
- ✅ Put the dictionary in a set so each piece check is almost instant.
Check Your Knowledge
Test what you learned. Pick an answer for each question, then click Check.
- 1
What does the Word Break problem ask you to return?
Why: Word Break asks for a true or false: can the whole string be split into dictionary words.
- 2
What does dp[i] mean in the tabulation?
Why: dp[i] is true when the prefix of length i can be split fully into dictionary words.
- 3
Why do we put the dictionary into a set?
Why: A set gives near-instant membership checks, while scanning a list for each piece is slow.
- 4
Why is plain recursion slow for Word Break?
Why: The same start position is solved many times across different paths, giving O(2ⁿ) without saving answers.