Description
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
- Create array with first of out of bounds index set as true
- Loop through the string length and then loop through the dictionary and check if substring matches
- If everything works then true will be set as true by the end of the loop
class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
boolean[] d = new boolean[s.length() + 1];
d[s.length()] = true;
for (int i = s.length() - 1; i >= 0; i--) {
for (String w: wordDict) {
if (i + w.length() <= s.length() &&
s.substring(i, i + w.length()).equals(w)) {
d[i] = d[i + w.length()];
}
if (d[i]) break;
}
}
return d[0];
}
}Primary Approach: 1D Dynamic Programming ( Time, Space)
Intuition
Define dp[i] as a boolean value indicating whether the prefix s[0 ... i-1] can be segmented into a valid sequence of dictionary words:
- Base Case:
dp[0] = truebecause an empty string can always be segmented. - Set & Max Word Length Optimization: Convert
wordDictto aHashSetfor average lookup time. Store the maximum length of any word inwordDict(maxLen) to avoid checking substrings longer than the longest word in the dictionary. - Transition: For each ending position
ifrom to , check all possible starting positionsjfrom to :- If
dp[j]istrueANDwordSet.contains(s.substring(j, i)), setdp[i] = trueand break early out of the inner loop.
- If
import java.util.HashSet;
import java.util.List;
import java.util.Set;
class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
Set<String> wordSet = new HashSet<>();
int maxLen = 0;
for (String word : wordDict) {
wordSet.add(word);
maxLen = Math.max(maxLen, word.length());
}
int n = s.length();
boolean[] dp = new boolean[n + 1];
dp[0] = true; // Base case: empty prefix is valid
for (int i = 1; i <= n; i++) {
// Only look back up to maxLen characters
for (int j = i - 1; j >= Math.max(0, i - maxLen); j--) {
if (dp[j] && wordSet.contains(s.substring(j, i))) {
dp[i] = true;
break; // Move to next position once a valid split is found
}
}
}
return dp[n];
}
}
Complexity
- Time Complexity: — Where is the length of
sand is the maximum word length inwordDict. The inner loop runs at most times, and string slicing (substring) takes time. - Space Complexity: — for the
dparray plus to store dictionary words of max length inside theHashSet.
Alternative Approach: Breadth-First Search (BFS) ( Time, Space)
Intuition
Treat the string indices as nodes in a graph, where a directed edge exists from index start to index end if s.substring(start, end) is present in wordDict:
- Maintain a
Queue<Integer>containing starting index0. - Use a
boolean[] visitedarray to prevent re-visiting the same start index multiple times. - For each popped
startindex, iterate through end positionsendfromstart + 1tomin(N, start + maxLen). - If
s.substring(start, end)exists in the dictionary and!visited[end]:- If
end == N, returntrue(reached the end of the string). - Mark
visited[end] = trueand pushendto the queue.
- If
import java.util.ArrayDeque;
import java.util.HashSet;
import java.util.List;
import java.util.Queue;
import java.util.Set;
class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
Set<String> wordSet = new HashSet<>(wordDict);
int maxLen = 0;
for (String word : wordDict) {
maxLen = Math.max(maxLen, word.length());
}
int n = s.length();
boolean[] visited = new boolean[n + 1];
Queue<Integer> queue = new ArrayDeque<>();
queue.add(0);
visited[0] = true;
while (!queue.isEmpty()) {
int start = queue.poll();
for (int end = start + 1; end <= Math.min(n, start + maxLen); end++) {
if (visited[end]) continue;
if (wordSet.contains(s.substring(start, end))) {
if (end == n) return true;
visited[end] = true;
queue.add(end);
}
}
}
return false;
}
}
Complexity
- Time Complexity: — Each index enters the queue at most once.
- Space Complexity: — Space for queue,
visitedarray, and dictionary hash set.
Key Interview Discussion Points
- Trie Alternative: A Trie can be constructed from
wordDictto perform prefix lookups character-by-character instead of callings.substring(), reducing substring creation overhead. - Importance of
maxLenOptimization: Unoptimized inner loops iterate from to , causing iterations. Limiting lookbacks to reduces runtime significantly when dictionary word lengths are bounded.
Easy Memory Rule
“
dp[i]is valid IFdp[j]was valid ANDs.substring(j, i)is inwordDict!”