Lesson 183 of 250
139. Word Break
NeetCode: Word Break · LeetCode: 139. Word Break · Difficulty: Medium
Given a string s and a list of words wordDict, return true if s can be split into a sequence of dictionary words. Words may be reused.
Example
Input: s = "leetcode", wordDict = ["leet", "code"] Output: true
Input: s = "applepenapple", wordDict = ["apple", "pen"] Output: true
Input: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"] Output: falseBrute force: recursion over the split point
can(start) answers whether s.substring(start) breaks into words. Try every end e; if s.substring(start, e) is a word and can(e) holds, the answer is true. Reaching the end of the string succeeds.
import java.util.HashSet;
import java.util.List;
import java.util.Set;
class Solution {
private boolean can(String s, int start, Set<String> dict) {
if (start == s.length()) {
return true;
}
for (int end = start + 1; end <= s.length(); end++) {
if (dict.contains(s.substring(start, end)) && can(s, end, dict)) {
return true;
}
}
return false;
}
public boolean wordBreak(String s, List<String> wordDict) {
return can(s, 0, new HashSet<>(wordDict));
}
}
public class Main {
public static void main(String[] args) {
Solution sol = new Solution();
System.out.println(sol.wordBreak("leetcode", List.of("leet", "code")));
System.out.println(sol.wordBreak("applepenapple", List.of("apple", "pen")));
System.out.println(sol.wordBreak("catsandog", List.of("cats", "dog", "sand", "and", "cat")));
System.out.println(sol.wordBreak("", List.of("a")));
}
}true
true
false
trueOn inputs like "aaaa...ab" with words a, aa, aaa, the same suffixes fail repeatedly through many different splits. Time: O(2^n) in the worst case. Space: O(n) stack.
Better: memoization
Whether the suffix s.substring(start) is breakable depends only on start. Caching it means each of the n suffixes is solved once. The memo is a byte[] with three states (not computed, true, false), because a plain boolean[] cannot tell "false" from "not computed". substring copies its characters, so each call costs time proportional to the substring length.
import java.util.HashSet;
import java.util.List;
import java.util.Set;
class Solution {
// memo[start]: 0 = not computed, 1 = true, 2 = false
private boolean can(String s, int start, Set<String> dict, byte[] memo) {
if (start == s.length()) {
return true;
}
if (memo[start] != 0) {
return memo[start] == 1;
}
boolean ok = false;
for (int end = start + 1; end <= s.length() && !ok; end++) {
ok = dict.contains(s.substring(start, end)) && can(s, end, dict, memo);
}
memo[start] = (byte) (ok ? 1 : 2);
return ok;
}
public boolean wordBreak(String s, List<String> wordDict) {
return can(s, 0, new HashSet<>(wordDict), new byte[s.length()]);
}
}
public class Main {
public static void main(String[] args) {
Solution sol = new Solution();
System.out.println(sol.wordBreak("leetcode", List.of("leet", "code")));
System.out.println(sol.wordBreak("applepenapple", List.of("apple", "pen")));
System.out.println(sol.wordBreak("catsandog", List.of("cats", "dog", "sand", "and", "cat")));
System.out.println(sol.wordBreak("", List.of("a")));
}
}true
true
false
trueTime: O(n^2) subproblem transitions, each hashing a substring of up to O(n) characters, so O(n^3) in the worst case. Space: O(n) plus the dictionary.
Optimal: bottom-up, bounded by the longest word
Let dp[i] mean "the first i characters can be broken into words", with dp[0] = true. For each i, look for a j < i with dp[j] true and s.substring(j, i) in the dictionary. A word is never longer than the longest dictionary word L, so only j >= i - L needs checking.
import java.util.HashSet;
import java.util.List;
import java.util.Set;
class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
Set<String> dict = new HashSet<>(wordDict);
int maxLen = 0;
for (String w : wordDict) {
maxLen = Math.max(maxLen, w.length());
}
int n = s.length();
boolean[] dp = new boolean[n + 1];
dp[0] = true;
for (int i = 1; i <= n; i++) {
for (int j = Math.max(0, i - maxLen); j < i; j++) {
if (dp[j] && dict.contains(s.substring(j, i))) {
dp[i] = true;
break;
}
}
}
return dp[n];
}
}
public class Main {
public static void main(String[] args) {
Solution sol = new Solution();
System.out.println(sol.wordBreak("leetcode", List.of("leet", "code")));
System.out.println(sol.wordBreak("applepenapple", List.of("apple", "pen")));
System.out.println(sol.wordBreak("catsandog", List.of("cats", "dog", "sand", "and", "cat")));
System.out.println(sol.wordBreak("", List.of("a")));
}
}true
true
false
trueTime: O(n · L^2), where L is the longest word: n · L pairs (j, i), each hashing up to L characters (so at most O(n^3) if a word is as long as the string). Space: O(n + total dictionary size).
