Difficulty | Source | Tags | |
---|---|---|---|
Medium |
160 Days of Problem Solving |
|
The problem can be found at the following link: Question Link
You are given a string s
and a list dictionary[]
of words. Your task is to determine whether the string s
can be formed by concatenating one or more words from the dictionary[]
.
Note: From dictionary[]
, any word can be taken any number of times and in any order.
s = "ilike"
dictionary[] = ["i", "like", "gfg"]
true
The given string s
can be broken down as "i like"
using words from the dictionary.
s = "ilikegfg"
dictionary[] = ["i", "like", "man", "india", "gfg"]
true
The given string s
can be broken down as "i like gfg"
using words from the dictionary.
s = "ilikemangoes"
dictionary[] = ["i", "like", "man", "india", "gfg"]
false
The given string s
cannot be formed using the dictionary words.
$( 1 \leq |s| \leq 3000 )$ $( 1 \leq |dictionary| \leq 1000 )$ $( 1 \leq |dictionary[i]| \leq 100 )$
- Use a boolean DP vector
dp
of size n+1 wheredp[i]
indicates if the substrings[0...i-1]
can be segmented. - Insert dictionary words into an unordered set for O(1) lookups.
- Iterate through the string and check all possible substrings using the dictionary.
- If a valid word is found and its prefix was segmentable, mark
dp[i+j]
as true. - Return
dp[n]
.
-
Expected Time Complexity:
$( O(n \times m) )$ , as each substring is checked in the dictionary. -
Expected Auxiliary Space Complexity:
$( O(n) )$ , as we use a DP array of sizen+1
.
class Solution {
public:
bool wordBreak(string s, vector<string>& d) {
unordered_set<string> u(d.begin(), d.end());
int n = s.size(), m = 0;
for(auto &w : d) m = max(m, (int)w.size());
vector<bool> dp(n + 1);
dp[0] = 1;
for(int i = 0; i < n; i++) {
if(!dp[i]) continue;
for(int j = 1; j <= m && i + j <= n; j++)
if(u.count(s.substr(i, j))) dp[i + j] = 1;
}
return dp[n];
}
};
- Use a boolean DP vector
dp
of size n+1 wheredp[i]
indicates if the substrings[0...i-1]
can be segmented. - Insert dictionary words into an unordered set for O(1) lookups.
- Iterate through the string and check all possible substrings using the dictionary.
- If a valid word is found and its prefix was segmentable, mark
dp[i+j]
as true. - Return
dp[n]
.
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict) {
unordered_set<string> dict(wordDict.begin(), wordDict.end());
int n = s.size(), maxLen = 0;
for (auto &w : wordDict) maxLen = max(maxLen, (int)w.size());
vector<bool> dp(n + 1, false);
dp[0] = true;
for (int i = 0; i < n; i++) {
if (!dp[i]) continue;
for (int j = 1; j <= maxLen && i + j <= n; j++) {
if (dict.count(s.substr(i, j))) dp[i + j] = true;
}
}
return dp[n];
}
};
✅ Time Complexity: O(n × m)
✅ Space Complexity: O(n)
- Build a Trie from the dictionary words.
- Use a DP vector where
dp[i]
is true ifs[i...n-1]
can be segmented. - Start from the end of the string, traverse the Trie, and mark segmentable indexes.
- Return
dp[0]
.
struct TrieNode {
bool isEnd;
unordered_map<char, TrieNode*> children;
TrieNode() : isEnd(false) {}
};
class Trie {
public:
TrieNode* root;
Trie() { root = new TrieNode(); }
void insert(const string &word) {
TrieNode* node = root;
for(char c : word) {
if(!node->children.count(c))
node->children[c] = new TrieNode();
node = node->children[c];
}
node->isEnd = true;
}
};
class Solution {
public:
bool wordBreak(string s, vector<string>& d) {
Trie trie;
for(auto &w: d) trie.insert(w);
int n = s.size();
vector<bool> dp(n + 1, false);
dp[n] = true;
for(int i = n - 1; i >= 0; i--) {
TrieNode* node = trie.root;
for(int j = i; j < n; j++){
char c = s[j];
if(!node->children.count(c)) break;
node = node->children[c];
if(node->isEnd && dp[j+1]){
dp[i] = true;
break;
}
}
}
return dp[0];
}
};
✅ Time Complexity: O(n × m), where m is the maximum word length
✅ Space Complexity: O(n + total characters in dictionary)
Approach | ⏱️ Time Complexity | 🗂️ Space Complexity | ✅ Pros | |
---|---|---|---|---|
Dynamic Programming (Optimized) | 🟢 O(n × m) | 🟢 O(n) | Efficient and easy to implement | Still requires full DP array |
Trie-Based Approach | 🟢 O(n × m) | 🟡 O(n + dictionary) | Faster lookups, avoids substrings | More complex implementation |
✅ Best Choice?
- If you want best efficiency: Use DP Optimized approach.
- If you prefer Trie-based lookup: Use Trie + DP.
class Solution {
public boolean wordBreak(String s, String[] d) {
Set<String> set = new HashSet<>(Arrays.asList(d));
int n = s.length(), m = 0;
for (String w : d) m = Math.max(m, w.length());
boolean[] dp = new boolean[n + 1];
dp[0] = true;
for (int i = 0; i < n; i++) {
if (!dp[i]) continue;
for (int j = 1; j <= m && i + j <= n; j++)
if (set.contains(s.substring(i, i + j))) dp[i + j] = true;
}
return dp[n];
}
}
class Solution:
def wordBreak(self, s, dictionary):
d = set(dictionary)
m = max((len(w) for w in dictionary), default=0)
n = len(s)
dp = [False] * (n + 1)
dp[0] = True
for i in range(n):
if not dp[i]:
continue
for j in range(1, m + 1):
if i + j <= n and s[i:i + j] in d:
dp[i + j] = True
return dp[n]
For discussions, questions, or doubts related to this solution, feel free to connect on LinkedIn: Any Questions. Let’s make this learning journey more collaborative!
⭐ If you find this helpful, please give this repository a star! ⭐