Given a string s and a dictionary of strings wordDict, return true if s can be segmented into a space-separated sequence of one or more dictionary words.
Note that the same word in the dictionary may be reused multiple times in the segmentation.
Example 1:
Input: s = “leetcode”, wordDict = [“leet”,“code”] Output: true Explanation: Return true because “leetcode” can be segmented as “leet code”.
Example 2:
Input: s = “applepenapple”, wordDict = [“apple”,“pen”] Output: true Explanation: Return true because “applepenapple” can be segmented as “apple pen apple”. Note that you are allowed to reuse a dictionary word.
Example 3:
Input: s = “catsandog”, wordDict = [“cats”,“dog”,“sand”,“and”,“cat”] Output: false
Constraints:
1 <= s.length <= 3001 <= wordDict.length <= 10001 <= wordDict[i].length <= 20sandwordDict[i]consist of only lowercase English letters.- All the strings of
wordDictare unique.
Approach: Dynamic Programming (1D)
Algorithm
- Let
dp[i]indicate whether the prefixs[0..i-1]can be segmented into dictionary words dp[0] = true(empty prefix)- For each end index
i, check every word: ifdp[i - len]is true ands.substring(i-len, i)equals the word, setdp[i] = true - Return
dp[s.length()]
Time & Space Complexity
- Time Complexity: O(n * m * L) - n is the string length, m is the dictionary size, and each substring comparison costs O(L)
- Space Complexity: O(n) - the DP array
Java Implementation
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class WordBreak {
/**
* Check whether s can be segmented into dictionary words.
* @param s Input string
* @param wordDict Dictionary of words
* @return true if s is segmentable
*/
public boolean wordBreak(String s, List<String> wordDict) {
Set<String> wordSet = new HashSet<>(wordDict);
boolean[] dp = new boolean[s.length() + 1];
dp[0] = true;
for (int i = 1; i <= s.length(); i++) {
for (String word : wordDict) {
int len = word.length();
if (i >= len && dp[i - len]
&& wordSet.contains(s.substring(i - len, i))) {
dp[i] = true;
break;
}
}
}
return dp[s.length()];
}
// Test method
public static void main(String[] args) {
WordBreak wb = new WordBreak();
System.out.println("Input: s = \"leetcode\", wordDict = [\"leet\",\"code\"]");
System.out.println("Output: " + wb.wordBreak("leetcode", List.of("leet", "code"))); // Expected: true
System.out.println("Input: s = \"catsandog\", wordDict = [\"cats\",\"dog\",\"sand\",\"and\",\"cat\"]");
System.out.println("Output: " + wb.wordBreak("catsandog",
List.of("cats", "dog", "sand", "and", "cat"))); // Expected: false
}
}
Alternative Approach: DP over Prefixes
public boolean wordBreakPrefix(String s, List<String> wordDict) {
Set<String> wordSet = new HashSet<>(wordDict);
boolean[] dp = new boolean[s.length() + 1];
dp[0] = true;
for (int i = 1; i <= s.length(); i++) {
for (int j = 0; j < i; j++) {
if (dp[j] && wordSet.contains(s.substring(j, i))) {
dp[i] = true;
break;
}
}
}
return dp[s.length()];
}
Example Walkthrough
For s = "leetcode", wordDict = ["leet","code"]:
- dp[0] = true
- At i=4, substring(0,4)=“leet” is in the set and dp[0] is true -> dp[4] = true
- At i=8, substring(4,8)=“code” is in the set and dp[4] is true -> dp[8] = true
- Return true
For s = "catsandog", no combination of the dictionary words covers the full string, so dp[8] stays false.
Key Points
- Overlapping Subproblems: Whether a prefix is segmentable is reused by later positions
- dp[0] = true: The empty prefix seeds the recursion
- Word Reuse: The dictionary set allows a word to be used any number of times
- Early Break: Once dp[i] is set, no further words need checking
- Two Variants: Iterating over words or over prefix split points both run in polynomial time