Word Break Problem in DSA Go - Time & Space Complexity
We want to know how the time needed to solve the Word Break Problem changes as the input grows.
Specifically, how does checking if a string can be split into dictionary words scale with string length?
Analyze the time complexity of the following code snippet.
func wordBreak(s string, wordDict []string) bool {
dp := make([]bool, len(s)+1)
dp[0] = true
for i := 1; i <= len(s); i++ {
for j := 0; j < i; j++ {
if dp[j] && contains(wordDict, s[j:i]) {
dp[i] = true
break
}
}
}
return dp[len(s)]
}
func contains(dict []string, word string) bool {
for _, w := range dict {
if w == word {
return true
}
}
return false
}
This code checks if the string s can be split into words from wordDict using dynamic programming.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Nested loops over the string length and checking substring membership in the dictionary.
- How many times: Outer loop runs n times, inner loop runs up to n times, and dictionary search runs up to m times per substring.
As the string length n grows, the number of substring checks grows roughly with n squared, and each check searches the dictionary of size m.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 10*10*m = 100*m |
| 100 | About 100*100*m = 10,000*m |
| 1000 | About 1000*1000*m = 1,000,000*m |
Pattern observation: Operations grow roughly with the square of the string length times the dictionary size.
Time Complexity: O(n^2 * m * k)
This means the time grows roughly with the square of the string length, multiplied by the number of dictionary words and average word length.
[X] Wrong: "The solution runs in linear time because it just loops over the string once."
[OK] Correct: The nested loops and dictionary checks cause the time to grow much faster than linear as the string gets longer.
Understanding this complexity helps you explain how dynamic programming solves problems efficiently and why dictionary lookup methods matter.
"What if we used a hash set for the dictionary instead of a list? How would the time complexity change?"
Practice
s and a dictionary of words wordDict?Solution
Step 1: Understand the problem statement
The Word Break Problem asks if the entire stringscan be split into valid words from the dictionary without leftover characters.Step 2: Differentiate from similar concepts
It is not enough forsto contain a substring or be equal to a dictionary word; the whole string must be fully segmented.Final Answer:
Ifscan be segmented into a sequence of dictionary words -> Option AQuick Check:
Word Break = Full segmentation check [OK]
- Thinking partial substring match is enough
- Confusing equality with segmentation
- Ignoring leftover characters after segmentation
Solution
Step 1: Identify correct parameter types
The input stringsshould be a string, and the dictionarywordDictshould be a slice of strings.Step 2: Identify correct return type
The function returns a boolean indicating if segmentation is possible.Final Answer:
func wordBreak(s string, wordDict []string) bool -> Option BQuick Check:
Input: string + []string, Output: bool [OK]
- Swapping parameter types
- Returning wrong data type
- Using map instead of slice without reason
func main() {
s := "leetcode"
wordDict := []string{"leet", "code"}
fmt.Println(wordBreak(s, wordDict))
}
func wordBreak(s string, wordDict []string) bool {
dp := make([]bool, len(s)+1)
dp[0] = true
for i := 1; i <= len(s); i++ {
for j := 0; j < i; j++ {
if dp[j] && contains(wordDict, s[j:i]) {
dp[i] = true
break
}
}
}
return dp[len(s)]
}
func contains(dict []string, word string) bool {
for _, w := range dict {
if w == word {
return true
}
}
return false
}Solution
Step 1: Understand the dynamic programming approach
The code uses a boolean slicedpwheredp[i]is true ifs[:i]can be segmented into dictionary words.Step 2: Trace the example string "leetcode"
"leet" and "code" are in the dictionary, sodp[len(s)]becomes true.Final Answer:
true -> Option AQuick Check:
DP segmentation = true [OK]
- Forgetting to break inner loop after true
- Incorrect substring slicing
- Not initializing dp[0] to true
func wordBreak(s string, wordDict []string) bool {
dp := make([]bool, len(s))
dp[0] = true
for i := 1; i <= len(s); i++ {
for j := 0; j < i; j++ {
if dp[j] && contains(wordDict, s[j:i]) {
dp[i] = true
break
}
}
}
return dp[len(s)]
}Solution
Step 1: Check dp slice size and initialization
The dp slice must be of sizelen(s)+1to include empty substring at index 0, and dp[0] should be true.Step 2: Verify loop bounds and dp usage
Loops use indices up tolen(s), so dp must have lengthlen(s)+1to avoid index out of range.Final Answer:
dp slice length should be len(s)+1 and dp[0] initialization is correct -> Option CQuick Check:
dp size = len(s)+1 [OK]
- Using dp length as len(s) causes index errors
- Not initializing dp[0] to true
- Incorrect loop ranges causing panic
s = "applepenapple" and dictionary wordDict = ["apple", "pen"], which of the following modifications to the Word Break algorithm will correctly handle repeated words without extra space complexity?Solution
Step 1: Understand repeated word handling
Repeated words cause repeated substring checks; memoization stores results to avoid recomputation.Step 2: Analyze options for efficiency
Memoization reduces time without increasing space beyond needed; queue or sorting do not directly optimize repeated checks; single boolean loses needed state.Final Answer:
Use a memoization map to store results of substring checks to avoid recomputation -> Option DQuick Check:
Memoization = efficient repeated checks [OK]
- Ignoring repeated substring computations
- Using single boolean loses segmentation info
- Assuming sorting dictionary helps substring checks
Ready to prove it?
Pass the test and this pattern moves unknown
