Given a non-empty string s and a dictionary dict[] containing a list of non-empty words, return all possible ways to break the string in individual dictionary words.
Note: The same word in the dictionary may be reused multiple times while breaking.
Examples:
Input: s = "likegfg" , dict[] = ["lik", "like", "egfg", "gfg"]
Output: ["lik egfg", "like gfg"]
Explanation: The string "likegfg" is segmented into valid dictionary words in all possible ways: "lik egfg" and "like gfg".
Input: s = "geeksforgeeks" , dict[] = ["for", "geeks"]
Output: ["geeks for geeks"]
Explanation: The string "geeksforgeeks" can be broken into valid words from the dictionary in one ways.Input: s = "aaa", dict[] = ["a", "aa"]
Output: ["a a a", "a aa", "aa a"]
Explanation:
"a a a" uses the word "a" three times.
"a aa" uses "a" followed by "aa".
"aa a" uses "aa" followed by "a".
The whole string "aaa" is not in the dictionary, so it cannot be used as a single word.
Table of Content
[Naive Approach] Backtracking - O((2^n)*n) Time and O(n) Space
The idea is to build the sentences word by word from left to right.
- At index start, we try every prefix nd if the prefix is a dictionary word, we add it to the current sentence and solve the rest of the string.
- When start reaches n, the whole string is covered, so the current words form one valid sentence.
- After exploring a word, we remove it (backtrack) and try the next prefix.
Working of the Approach:
- Put all words of dict[] in a set, so that checking whether a prefix is a valid word takes O(1) on average.
- Create an empty result list and an empty list path[] that holds the words of the current sentence.
- Run a recursive function with start = 0.
- In the function, if start == n, join the words of path[] with spaces, add this sentence to the result, and return.
- Otherwise, for every end from start + 1 to n, take the prefix s[start..end).
- If the prefix is in the set, add it to path[], call the function with start = end, and then remove it from path[].
- Return the result list.
#include <iostream>
#include <vector>
#include <string>
#include <unordered_set>
#include <algorithm>
using namespace std;
// builds sentences from s[start..], where path holds the words chosen so far
void backtrack(string& s, int start, unordered_set<string>& st,
vector<string>& path, vector<string>& res) {
// the whole string is covered, so the words in path form one sentence
if (start == s.size()) {
string sentence = path[0];
for (int i = 1; i < path.size(); i++)
sentence += " " + path[i];
res.push_back(sentence);
return;
}
// try every prefix of the remaining string
for (int end = start + 1; end <= s.size(); end++) {
string word = s.substr(start, end - start);
// continue only if the prefix is a valid dictionary word
if (st.count(word)) {
path.push_back(word);
backtrack(s, end, st, path, res);
// remove the word to try the next prefix
path.pop_back();
}
}
}
vector<string> wordBreak(vector<string>& dict, string& s) {
// set of words for fast lookups
unordered_set<string> st(dict.begin(), dict.end());
vector<string> res;
vector<string> path;
backtrack(s, 0, st, path, res);
return res;
}
int main() {
vector<string> dict = {"lik", "like", "egfg", "gfg"};
string s = "likegfg";
vector<string> res = wordBreak(dict, s);
sort(res.begin(), res.end());
cout << "[";
for (int i = 0; i < res.size(); i++) {
if (i > 0)
cout << ", ";
cout << "\"" << res[i] << "\"";
}
cout << "]" << endl;
return 0;
}
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
class GFG {
// builds sentences from s[start..], where path holds the words chosen so far
static void backtrack(String s, int start, HashSet<String> st,
ArrayList<String> path, ArrayList<String> res) {
// the whole string is covered, so the words in path form one sentence
if (start == s.length()) {
res.add(String.join(" ", path));
return;
}
// try every prefix of the remaining string
for (int end = start + 1; end <= s.length(); end++) {
String word = s.substring(start, end);
// continue only if the prefix is a valid dictionary word
if (st.contains(word)) {
path.add(word);
backtrack(s, end, st, path, res);
// remove the word to try the next prefix
path.remove(path.size() - 1);
}
}
}
static ArrayList<String> wordBreak(String[] dict, String s) {
// set of words for fast lookups
HashSet<String> st = new HashSet<>(Arrays.asList(dict));
ArrayList<String> res = new ArrayList<>();
ArrayList<String> path = new ArrayList<>();
backtrack(s, 0, st, path, res);
return res;
}
public static void main(String[] args) {
String[] dict = {"lik", "like", "egfg", "gfg"};
String s = "likegfg";
ArrayList<String> res = wordBreak(dict, s);
Collections.sort(res);
StringBuilder sb = new StringBuilder("[");
for (int i = 0; i < res.size(); i++) {
if (i > 0)
sb.append(", ");
sb.append("\"").append(res.get(i)).append("\"");
}
sb.append("]");
System.out.println(sb);
}
}
# builds sentences from s[start..], where path holds the words chosen so far
def backtrack(s, start, st, path, res):
# the whole string is covered, so the words in path form one sentence
if start == len(s):
res.append(" ".join(path))
return
# try every prefix of the remaining string
for end in range(start + 1, len(s) + 1):
word = s[start:end]
# continue only if the prefix is a valid dictionary word
if word in st:
path.append(word)
backtrack(s, end, st, path, res)
# remove the word to try the next prefix
path.pop()
def wordBreak(dict, s):
# set of words for fast lookups
st = set(dict)
res = []
path = []
backtrack(s, 0, st, path, res)
return res
if __name__ == "__main__":
dict = ["lik", "like", "egfg", "gfg"]
s = "likegfg"
res = wordBreak(dict, s)
res.sort()
print("[" + ", ".join('"' + w + '"' for w in res) + "]")
using System;
using System.Collections.Generic;
class GFG {
// builds sentences from s[start..], where path holds the words chosen so far
static void backtrack(string s, int start, HashSet<string> st,
List<string> path, List<string> res) {
// the whole string is covered, so the words in path form one sentence
if (start == s.Length) {
res.Add(string.Join(" ", path));
return;
}
// try every prefix of the remaining string
for (int end = start + 1; end <= s.Length; end++) {
string word = s.Substring(start, end - start);
// continue only if the prefix is a valid dictionary word
if (st.Contains(word)) {
path.Add(word);
backtrack(s, end, st, path, res);
// remove the word to try the next prefix
path.RemoveAt(path.Count - 1);
}
}
}
static List<string> wordBreak(string[] dict, string s) {
// set of words for fast lookups
HashSet<string> st = new HashSet<string>(dict);
List<string> res = new List<string>();
List<string> path = new List<string>();
backtrack(s, 0, st, path, res);
return res;
}
static void Main() {
string[] dict = {"lik", "like", "egfg", "gfg"};
string s = "likegfg";
List<string> res = wordBreak(dict, s);
res.Sort(string.CompareOrdinal);
string output = "[";
for (int i = 0; i < res.Count; i++) {
if (i > 0)
output += ", ";
output += "\"" + res[i] + "\"";
}
output += "]";
Console.WriteLine(output);
}
}
// builds sentences from s[start..], where path holds the words chosen so far
function backtrack(s, start, st, path, res) {
// the whole string is covered, so the words in path form one sentence
if (start === s.length) {
res.push(path.join(" "));
return;
}
// try every prefix of the remaining string
for (let end = start + 1; end <= s.length; end++) {
const word = s.substring(start, end);
// continue only if the prefix is a valid dictionary word
if (st.has(word)) {
path.push(word);
backtrack(s, end, st, path, res);
// remove the word to try the next prefix
path.pop();
}
}
}
function wordBreak(dict, s) {
// set of words for fast lookups
const st = new Set(dict);
const res = [];
const path = [];
backtrack(s, 0, st, path, res);
return res;
}
// Driver Code
const dict = ["lik", "like", "egfg", "gfg"];
const s = "likegfg";
const res = wordBreak(dict, s);
res.sort();
console.log("[" + res.map(w => '"' + w + '"').join(", ") + "]");
Output
["lik egfg", "like gfg"]
[Better Approach 1] Top Down DP or Memoization - O((n^2)*m) Time and O(n*m) Space
- We use an array of strings memo[] to store all sentences starting from every index.
- Like above approach, for each substring starting at start, if it exists in the dictionary, we recursively compute all valid sentence breaks from the next index (end).
- Before solving any index, we check if its result is already stored in memo to avoid recomputation.
Working of the Approach:
- Put all the words of dict[] in a set st, and create an empty map memo, which will store the sentences of every index.
- Write a function wordBreakHelper(start) that returns all the sentences that can be formed from s[start..n).
- If memo already has start, return the stored sentences.
- If start is equal to n, return a list with one empty string. It marks that the whole string is split.
- For every end from start + 1 to n, take word = s[start..end). If word is in st, call wordBreakHelper(end) to get subSentences, and add word in front of each of them (just word, if the sentence is empty).
- Store all these sentences in memo[start], and return them. The answer is wordBreakHelper(0).
#include <iostream>
#include <vector>
#include <string>
#include <unordered_set>
#include <unordered_map>
#include <algorithm>
using namespace std;
// returns all the sentences that can be formed from s[start..n)
vector<string> wordBreakHelper(string& s, unordered_set<string>& st,
unordered_map<int, vector<string>>& memo, int start) {
int n = s.size();
// the sentences of this index are already computed, so return them
if (memo.count(start))
return memo[start];
vector<string> result;
// the end of the string is reached, so add the empty sentence to mark a valid split
if (start == n) {
result.push_back("");
return result;
}
// try every substring that starts at index start
for (int end = start + 1; end <= n; end++) {
string word = s.substr(start, end - start);
// continue only if the substring is a valid dictionary word
if (st.count(word)) {
// find all the sentences that can be formed from s[end..n)
vector<string> subSentences = wordBreakHelper(s, st, memo, end);
// put the word in front of every one of them
for (string& sub : subSentences) {
if (sub.empty())
result.push_back(word);
else
result.push_back(word + " " + sub);
}
}
}
// store the sentences of this index, so that they are not computed again
memo[start] = result;
return result;
}
vector<string> wordBreak(vector<string>& dict, string& s) {
// set of words for fast lookups
unordered_set<string> st(dict.begin(), dict.end());
unordered_map<int, vector<string>> memo;
return wordBreakHelper(s, st, memo, 0);
}
int main() {
vector<string> dict = {"lik", "like", "egfg", "gfg"};
string s = "likegfg";
vector<string> res = wordBreak(dict, s);
sort(res.begin(), res.end());
cout << "[";
for (int i = 0; i < res.size(); i++) {
if (i > 0)
cout << ", ";
cout << "\"" << res[i] << "\"";
}
cout << "]" << endl;
return 0;
}
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
class GFG {
// returns all the sentences that can be formed from s[start..n)
static ArrayList<String> wordBreakHelper(String s, HashSet<String> st,
HashMap<Integer, ArrayList<String>> memo, int start) {
int n = s.length();
// the sentences of this index are already computed, so return them
if (memo.containsKey(start))
return memo.get(start);
ArrayList<String> result = new ArrayList<>();
// the end of the string is reached, so add the empty sentence to mark a valid split
if (start == n) {
result.add("");
return result;
}
// try every substring that starts at index start
for (int end = start + 1; end <= n; end++) {
String word = s.substring(start, end);
// continue only if the substring is a valid dictionary word
if (st.contains(word)) {
// find all the sentences that can be formed from s[end..n)
ArrayList<String> subSentences = wordBreakHelper(s, st, memo, end);
// put the word in front of every one of them
for (String sub : subSentences) {
if (sub.isEmpty())
result.add(word);
else
result.add(word + " " + sub);
}
}
}
// store the sentences of this index, so that they are not computed again
memo.put(start, result);
return result;
}
static ArrayList<String> wordBreak(String[] dict, String s) {
// set of words for fast lookups
HashSet<String> st = new HashSet<>(Arrays.asList(dict));
HashMap<Integer, ArrayList<String>> memo = new HashMap<>();
return wordBreakHelper(s, st, memo, 0);
}
public static void main(String[] args) {
String[] dict = {"lik", "like", "egfg", "gfg"};
String s = "likegfg";
ArrayList<String> res = wordBreak(dict, s);
Collections.sort(res);
StringBuilder sb = new StringBuilder("[");
for (int i = 0; i < res.size(); i++) {
if (i > 0)
sb.append(", ");
sb.append("\"").append(res.get(i)).append("\"");
}
sb.append("]");
System.out.println(sb);
}
}
# returns all the sentences that can be formed from s[start..n)
def wordBreakHelper(s, st, memo, start):
n = len(s)
# the sentences of this index are already computed, so return them
if start in memo:
return memo[start]
result = []
# the end of the string is reached, so add the empty sentence to mark a valid split
if start == n:
result.append("")
return result
# try every substring that starts at index start
for end in range(start + 1, n + 1):
word = s[start:end]
# continue only if the substring is a valid dictionary word
if word in st:
# find all the sentences that can be formed from s[end..n)
subSentences = wordBreakHelper(s, st, memo, end)
# put the word in front of every one of them
for sub in subSentences:
if sub == "":
result.append(word)
else:
result.append(word + " " + sub)
# store the sentences of this index, so that they are not computed again
memo[start] = result
return result
def wordBreak(dict, s):
# set of words for fast lookups
st = set(dict)
memo = {}
return wordBreakHelper(s, st, memo, 0)
if __name__ == "__main__":
dict = ["lik", "like", "egfg", "gfg"]
s = "likegfg"
res = wordBreak(dict, s)
res.sort()
print("[" + ", ".join('"' + w + '"' for w in res) + "]")
using System;
using System.Collections.Generic;
class GFG {
// returns all the sentences that can be formed from s[start..n)
static List<string> wordBreakHelper(string s, HashSet<string> st,
Dictionary<int, List<string>> memo, int start) {
int n = s.Length;
// the sentences of this index are already computed, so return them
if (memo.ContainsKey(start))
return memo[start];
List<string> result = new List<string>();
// the end of the string is reached, so add the empty sentence to mark a valid split
if (start == n) {
result.Add("");
return result;
}
// try every substring that starts at index start
for (int end = start + 1; end <= n; end++) {
string word = s.Substring(start, end - start);
// continue only if the substring is a valid dictionary word
if (st.Contains(word)) {
// find all the sentences that can be formed from s[end..n)
List<string> subSentences = wordBreakHelper(s, st, memo, end);
// put the word in front of every one of them
foreach (string sub in subSentences) {
if (sub.Length == 0)
result.Add(word);
else
result.Add(word + " " + sub);
}
}
}
// store the sentences of this index, so that they are not computed again
memo[start] = result;
return result;
}
static List<string> wordBreak(string[] dict, string s) {
// set of words for fast lookups
HashSet<string> st = new HashSet<string>(dict);
Dictionary<int, List<string>> memo = new Dictionary<int, List<string>>();
return wordBreakHelper(s, st, memo, 0);
}
static void Main() {
string[] dict = {"lik", "like", "egfg", "gfg"};
string s = "likegfg";
List<string> res = wordBreak(dict, s);
res.Sort(string.CompareOrdinal);
string output = "[";
for (int i = 0; i < res.Count; i++) {
if (i > 0)
output += ", ";
output += "\"" + res[i] + "\"";
}
output += "]";
Console.WriteLine(output);
}
}
// returns all the sentences that can be formed from s[start..n)
function wordBreakHelper(s, st, memo, start) {
const n = s.length;
// the sentences of this index are already computed, so return them
if (memo.has(start))
return memo.get(start);
const result = [];
// the end of the string is reached, so add the empty sentence to mark a valid split
if (start === n) {
result.push("");
return result;
}
// try every substring that starts at index start
for (let end = start + 1; end <= n; end++) {
const word = s.substring(start, end);
// continue only if the substring is a valid dictionary word
if (st.has(word)) {
// find all the sentences that can be formed from s[end..n)
const subSentences = wordBreakHelper(s, st, memo, end);
// put the word in front of every one of them
for (const sub of subSentences) {
if (sub === "")
result.push(word);
else
result.push(word + " " + sub);
}
}
}
// store the sentences of this index, so that they are not computed again
memo.set(start, result);
return result;
}
function wordBreak(dict, s) {
// set of words for fast lookups
const st = new Set(dict);
const memo = new Map();
return wordBreakHelper(s, st, memo, 0);
}
// Driver Code
const dict = ["lik", "like", "egfg", "gfg"];
const s = "likegfg";
const res = wordBreak(dict, s);
res.sort();
console.log("[" + res.map(w => '"' + w + '"').join(", ") + "]");
Output
["lik egfg", "like gfg"]
[Better Approach 2] Bottom Up DP (Tabulation) - O((n^2)*m) Time and O(n*m) Space
In the bottom-up DP approach, we create an array dp where dp[i] stores all valid sentences that can be formed starting from index i.
We process the string from right to left, and for each index i, we check every substring s[i:j].
If the substring exists in the dictionary, we combine it with all valid sentences already stored in dp[j] and add the formed sentences to dp[i].
Working of the Approach:
- Put all the words of dict[] in a set st.
- Create a list dp of size n + 1, where dp[i] stores all the sentences that can be formed from s[i..n). Every dp[i] is empty at the start.
- Set dp[n] to a list with one empty string. It marks the end of the string.
- Go through i from n - 1 down to 0. For every j from i + 1 to n, take word = s[i..j).
- If word is in st, add word in front of every sentence of dp[j] (just word, if the sentence is empty), and put the results in dp[i].
- Return dp[0].
#include <iostream>
#include <vector>
#include <string>
#include <unordered_set>
#include <algorithm>
using namespace std;
vector<string> wordBreak(vector<string>& dict, string& s) {
int n = s.size();
// set of words for fast lookups
unordered_set<string> st(dict.begin(), dict.end());
// dp[i] stores all the sentences that can be formed from s[i..n),
// and dp[n] holds the empty sentence, which stands for the end of the string
vector<vector<string>> dp(n + 1);
dp[n] = {""};
// process the string from right to left
for (int i = n - 1; i >= 0; i--) {
// check every substring that starts at index i
for (int j = i + 1; j <= n; j++) {
string word = s.substr(i, j - i);
// continue only if the substring is a valid dictionary word
if (st.count(word)) {
// put the word in front of every sentence that can be formed from s[j..n)
for (string& sub : dp[j]) {
if (sub.empty())
dp[i].push_back(word);
else
dp[i].push_back(word + " " + sub);
}
}
}
}
return dp[0];
}
int main() {
vector<string> dict = {"lik", "like", "egfg", "gfg"};
string s = "likegfg";
vector<string> res = wordBreak(dict, s);
sort(res.begin(), res.end());
cout << "[";
for (int i = 0; i < res.size(); i++) {
if (i > 0)
cout << ", ";
cout << "\"" << res[i] << "\"";
}
cout << "]" << endl;
return 0;
}
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
class GFG {
static ArrayList<String> wordBreak(String[] dict, String s) {
int n = s.length();
// set of words for fast lookups
HashSet<String> st = new HashSet<>(Arrays.asList(dict));
// dp.get(i) stores all the sentences that can be formed from s[i..n),
// and dp.get(n) holds the empty sentence, which stands for the end of the string
ArrayList<ArrayList<String>> dp = new ArrayList<>();
for (int i = 0; i <= n; i++)
dp.add(new ArrayList<>());
dp.get(n).add("");
// process the string from right to left
for (int i = n - 1; i >= 0; i--) {
// check every substring that starts at index i
for (int j = i + 1; j <= n; j++) {
String word = s.substring(i, j);
// continue only if the substring is a valid dictionary word
if (st.contains(word)) {
// put the word in front of every sentence that can be formed from s[j..n)
for (String sub : dp.get(j)) {
if (sub.isEmpty())
dp.get(i).add(word);
else
dp.get(i).add(word + " " + sub);
}
}
}
}
return dp.get(0);
}
public static void main(String[] args) {
String[] dict = {"lik", "like", "egfg", "gfg"};
String s = "likegfg";
ArrayList<String> res = wordBreak(dict, s);
Collections.sort(res);
StringBuilder sb = new StringBuilder("[");
for (int i = 0; i < res.size(); i++) {
if (i > 0)
sb.append(", ");
sb.append("\"").append(res.get(i)).append("\"");
}
sb.append("]");
System.out.println(sb);
}
}
def wordBreak(dict, s):
n = len(s)
# set of words for fast lookups
st = set(dict)
# dp[i] stores all the sentences that can be formed from s[i..n),
# and dp[n] holds the empty sentence, which stands for the end of the string
dp = [[] for _ in range(n + 1)]
dp[n] = [""]
# process the string from right to left
for i in range(n - 1, -1, -1):
# check every substring that starts at index i
for j in range(i + 1, n + 1):
word = s[i:j]
# continue only if the substring is a valid dictionary word
if word in st:
# put the word in front of every sentence that can be formed from s[j..n)
for sub in dp[j]:
if sub == "":
dp[i].append(word)
else:
dp[i].append(word + " " + sub)
return dp[0]
if __name__ == "__main__":
dict = ["lik", "like", "egfg", "gfg"]
s = "likegfg"
res = wordBreak(dict, s)
res.sort()
print("[" + ", ".join('"' + w + '"' for w in res) + "]")
using System;
using System.Collections.Generic;
class GFG {
static List<string> wordBreak(string[] dict, string s) {
int n = s.Length;
// set of words for fast lookups
HashSet<string> st = new HashSet<string>(dict);
// dp[i] stores all the sentences that can be formed from s[i..n),
// and dp[n] holds the empty sentence, which stands for the end of the string
List<List<string>> dp = new List<List<string>>();
for (int i = 0; i <= n; i++)
dp.Add(new List<string>());
dp[n].Add("");
// process the string from right to left
for (int i = n - 1; i >= 0; i--) {
// check every substring that starts at index i
for (int j = i + 1; j <= n; j++) {
string word = s.Substring(i, j - i);
// continue only if the substring is a valid dictionary word
if (st.Contains(word)) {
// put the word in front of every sentence that can be formed from s[j..n)
foreach (string sub in dp[j]) {
if (sub.Length == 0)
dp[i].Add(word);
else
dp[i].Add(word + " " + sub);
}
}
}
}
return dp[0];
}
static void Main() {
string[] dict = {"lik", "like", "egfg", "gfg"};
string s = "likegfg";
List<string> res = wordBreak(dict, s);
res.Sort(string.CompareOrdinal);
string output = "[";
for (int i = 0; i < res.Count; i++) {
if (i > 0)
output += ", ";
output += "\"" + res[i] + "\"";
}
output += "]";
Console.WriteLine(output);
}
}
function wordBreak(dict, s) {
const n = s.length;
// set of words for fast lookups
const st = new Set(dict);
// dp[i] stores all the sentences that can be formed from s[i..n),
// and dp[n] holds the empty sentence, which stands for the end of the string
const dp = Array.from({ length: n + 1 }, () => []);
dp[n].push("");
// process the string from right to left
for (let i = n - 1; i >= 0; i--) {
// check every substring that starts at index i
for (let j = i + 1; j <= n; j++) {
const word = s.substring(i, j);
// continue only if the substring is a valid dictionary word
if (st.has(word)) {
// put the word in front of every sentence that can be formed from s[j..n)
for (const sub of dp[j]) {
if (sub === "")
dp[i].push(word);
else
dp[i].push(word + " " + sub);
}
}
}
}
return dp[0];
}
// Driver Code
const dict = ["lik", "like", "egfg", "gfg"];
const s = "likegfg";
const res = wordBreak(dict, s);
res.sort();
console.log("[" + res.map(w => '"' + w + '"').join(", ") + "]");
Output
["lik egfg", "like gfg"]
[Expected Approach] Feasibility DP + Backtracking
Plain backtracking wastes time on dead ends, which are positions from where the rest of the string can never be split into dictionary words. We remove them in two steps:
- First, compute canBreak[i], which is true if the suffix s[i..n) can be split into dictionary words.
- Then run backtracking, and move to a position end only if canBreak[end] is true, so every recursive call leads to at least one complete sentence.
Working of the Approach:
- Put all words of dict[] in a set, and find L, the length of the longest word.
- Create a boolean array canBreak[] of size n + 1, with all values false except canBreak[n] = true, since an empty suffix is always valid.
- Traverse i from n - 1 down to 0. For every end from i + 1 to min(n, i + L), if canBreak[end] is true and s[i..end) is in the set, set canBreak[i] = true and stop checking this i.
- If canBreak[0] is false, no sentence is possible, so return an empty list.
- Create an empty result list and an empty list path[] that holds the words of the current sentence.
- Run a recursive function with start = 0. If start == n, join the words of path[] with spaces, add the sentence to the result, and return.
- Otherwise, for every end from start + 1 to min(n, start + L), take the word s[start..end). If canBreak[end] is true and the word is in the set, add it to path[], call the function with start = end, and then remove it from path[].
- Return the result list.
Let us understand with an example:
Consider s = "likegfg", dict[] = ["lik", "like", "egfg", "gfg"].
- canBreak[7] = true, since the empty suffix is always valid.
- canBreak[4] = true, because the suffix "gfg" is in the dictionary.
- canBreak[3] = true, because the suffix "egfg" is in the dictionary.
- canBreak[0] = true, because the prefix "lik" is a word and canBreak[3] is true.
- canBreak[1], canBreak[2], canBreak[5] and canBreak[6] = false, because no valid word continues from these positions.
- Backtracking from index 0: only "lik" (leads to index 3) and "like" (leads to index 4) are tried, since canBreak[3] and canBreak[4] are true.
- From index 3, "egfg" leads to the end, so "lik egfg" is added. From index 4, "gfg" leads to the end, so "like gfg" is added.
- If the string were "aaab" with dict[] = ["a", "aa"], then canBreak[0] would be false, and we would return an empty list immediately, without exploring any split.
#include <iostream>
#include <vector>
#include <string>
#include <unordered_set>
#include <algorithm>
using namespace std;
// builds sentences from s[start..], where path holds the words chosen so far
void backtrack(string& s, int start, int maxLen, unordered_set<string>& st,
vector<bool>& canBreak, vector<string>& path, vector<string>& res) {
// the whole string is covered, so the words in path form one sentence
if (start == s.size()) {
string sentence = path[0];
for (int i = 1; i < path.size(); i++)
sentence += " " + path[i];
res.push_back(sentence);
return;
}
// try every prefix of the remaining string, up to the longest word
for (int end = start + 1; end <= min((int)s.size(), start + maxLen); end++) {
// move ahead only if the rest of the string can be broken
if (!canBreak[end])
continue;
string word = s.substr(start, end - start);
// continue only if the prefix is a valid dictionary word
if (st.count(word)) {
path.push_back(word);
backtrack(s, end, maxLen, st, canBreak, path, res);
// remove the word to try the next prefix
path.pop_back();
}
}
}
vector<string> wordBreak(vector<string>& dict, string& s) {
int n = s.size();
// set of words for fast lookups, and the length of the longest word
unordered_set<string> st(dict.begin(), dict.end());
int maxLen = 0;
for (string& w : dict)
maxLen = max(maxLen, (int)w.size());
// canBreak[i] is true if s[i..n) can be split into dictionary words,
// and an empty suffix is always valid
vector<bool> canBreak(n + 1, false);
canBreak[n] = true;
for (int i = n - 1; i >= 0; i--) {
for (int end = i + 1; end <= min(n, i + maxLen); end++) {
if (canBreak[end] && st.count(s.substr(i, end - i))) {
canBreak[i] = true;
break;
}
}
}
vector<string> res;
// no sentence is possible, so no split needs to be explored
if (!canBreak[0])
return res;
vector<string> path;
backtrack(s, 0, maxLen, st, canBreak, path, res);
return res;
}
int main() {
vector<string> dict = {"lik", "like", "egfg", "gfg"};
string s = "likegfg";
vector<string> res = wordBreak(dict, s);
sort(res.begin(), res.end());
cout << "[";
for (int i = 0; i < res.size(); i++) {
if (i > 0)
cout << ", ";
cout << "\"" << res[i] << "\"";
}
cout << "]" << endl;
return 0;
}
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
class GFG {
// builds sentences from s[start..], where path holds the words chosen so far
static void backtrack(String s, int start, int maxLen, HashSet<String> st,
boolean[] canBreak, ArrayList<String> path,
ArrayList<String> res) {
// the whole string is covered, so the words in path form one sentence
if (start == s.length()) {
res.add(String.join(" ", path));
return;
}
// try every prefix of the remaining string, up to the longest word
for (int end = start + 1; end <= Math.min(s.length(), start + maxLen); end++) {
// move ahead only if the rest of the string can be broken
if (!canBreak[end])
continue;
String word = s.substring(start, end);
// continue only if the prefix is a valid dictionary word
if (st.contains(word)) {
path.add(word);
backtrack(s, end, maxLen, st, canBreak, path, res);
// remove the word to try the next prefix
path.remove(path.size() - 1);
}
}
}
static ArrayList<String> wordBreak(String[] dict, String s) {
int n = s.length();
// set of words for fast lookups, and the length of the longest word
HashSet<String> st = new HashSet<>(Arrays.asList(dict));
int maxLen = 0;
for (String w : dict)
maxLen = Math.max(maxLen, w.length());
// canBreak[i] is true if s[i..n) can be split into dictionary words,
// and an empty suffix is always valid
boolean[] canBreak = new boolean[n + 1];
canBreak[n] = true;
for (int i = n - 1; i >= 0; i--) {
for (int end = i + 1; end <= Math.min(n, i + maxLen); end++) {
if (canBreak[end] && st.contains(s.substring(i, end))) {
canBreak[i] = true;
break;
}
}
}
ArrayList<String> res = new ArrayList<>();
// no sentence is possible, so no split needs to be explored
if (!canBreak[0])
return res;
ArrayList<String> path = new ArrayList<>();
backtrack(s, 0, maxLen, st, canBreak, path, res);
return res;
}
public static void main(String[] args) {
String[] dict = {"lik", "like", "egfg", "gfg"};
String s = "likegfg";
ArrayList<String> res = wordBreak(dict, s);
Collections.sort(res);
StringBuilder sb = new StringBuilder("[");
for (int i = 0; i < res.size(); i++) {
if (i > 0)
sb.append(", ");
sb.append("\"").append(res.get(i)).append("\"");
}
sb.append("]");
System.out.println(sb);
}
}
# builds sentences from s[start..], where path holds the words chosen so far
def backtrack(s, start, maxLen, st, canBreak, path, res):
# the whole string is covered, so the words in path form one sentence
if start == len(s):
res.append(" ".join(path))
return
# try every prefix of the remaining string, up to the longest word
for end in range(start + 1, min(len(s), start + maxLen) + 1):
# move ahead only if the rest of the string can be broken
if not canBreak[end]:
continue
word = s[start:end]
# continue only if the prefix is a valid dictionary word
if word in st:
path.append(word)
backtrack(s, end, maxLen, st, canBreak, path, res)
# remove the word to try the next prefix
path.pop()
def wordBreak(dict, s):
n = len(s)
# set of words for fast lookups, and the length of the longest word
st = set(dict)
maxLen = max(len(w) for w in dict)
# canBreak[i] is true if s[i..n) can be split into dictionary words,
# and an empty suffix is always valid
canBreak = [False] * (n + 1)
canBreak[n] = True
for i in range(n - 1, -1, -1):
for end in range(i + 1, min(n, i + maxLen) + 1):
if canBreak[end] and s[i:end] in st:
canBreak[i] = True
break
res = []
# no sentence is possible, so no split needs to be explored
if not canBreak[0]:
return res
path = []
backtrack(s, 0, maxLen, st, canBreak, path, res)
return res
if __name__ == "__main__":
dict = ["lik", "like", "egfg", "gfg"]
s = "likegfg"
res = wordBreak(dict, s)
res.sort()
print("[" + ", ".join('"' + w + '"' for w in res) + "]")
using System;
using System.Collections.Generic;
class GFG {
// builds sentences from s[start..], where path holds the words chosen so far
static void backtrack(string s, int start, int maxLen, HashSet<string> st,
bool[] canBreak, List<string> path, List<string> res) {
// the whole string is covered, so the words in path form one sentence
if (start == s.Length) {
res.Add(string.Join(" ", path));
return;
}
// try every prefix of the remaining string, up to the longest word
for (int end = start + 1; end <= Math.Min(s.Length, start + maxLen); end++) {
// move ahead only if the rest of the string can be broken
if (!canBreak[end])
continue;
string word = s.Substring(start, end - start);
// continue only if the prefix is a valid dictionary word
if (st.Contains(word)) {
path.Add(word);
backtrack(s, end, maxLen, st, canBreak, path, res);
// remove the word to try the next prefix
path.RemoveAt(path.Count - 1);
}
}
}
static List<string> wordBreak(string[] dict, string s) {
int n = s.Length;
// set of words for fast lookups, and the length of the longest word
HashSet<string> st = new HashSet<string>(dict);
int maxLen = 0;
foreach (string w in dict)
maxLen = Math.Max(maxLen, w.Length);
// canBreak[i] is true if s[i..n) can be split into dictionary words,
// and an empty suffix is always valid
bool[] canBreak = new bool[n + 1];
canBreak[n] = true;
for (int i = n - 1; i >= 0; i--) {
for (int end = i + 1; end <= Math.Min(n, i + maxLen); end++) {
if (canBreak[end] && st.Contains(s.Substring(i, end - i))) {
canBreak[i] = true;
break;
}
}
}
List<string> res = new List<string>();
// no sentence is possible, so no split needs to be explored
if (!canBreak[0])
return res;
List<string> path = new List<string>();
backtrack(s, 0, maxLen, st, canBreak, path, res);
return res;
}
static void Main() {
string[] dict = {"lik", "like", "egfg", "gfg"};
string s = "likegfg";
List<string> res = wordBreak(dict, s);
res.Sort(string.CompareOrdinal);
string output = "[";
for (int i = 0; i < res.Count; i++) {
if (i > 0)
output += ", ";
output += "\"" + res[i] + "\"";
}
output += "]";
Console.WriteLine(output);
}
}
// builds sentences from s[start..], where path holds the words chosen so far
function backtrack(s, start, maxLen, st, canBreak, path, res) {
// the whole string is covered, so the words in path form one sentence
if (start === s.length) {
res.push(path.join(" "));
return;
}
// try every prefix of the remaining string, up to the longest word
for (let end = start + 1; end <= Math.min(s.length, start + maxLen); end++) {
// move ahead only if the rest of the string can be broken
if (!canBreak[end])
continue;
const word = s.substring(start, end);
// continue only if the prefix is a valid dictionary word
if (st.has(word)) {
path.push(word);
backtrack(s, end, maxLen, st, canBreak, path, res);
// remove the word to try the next prefix
path.pop();
}
}
}
function wordBreak(dict, s) {
const n = s.length;
// set of words for fast lookups, and the length of the longest word
const st = new Set(dict);
let maxLen = 0;
for (const w of dict)
maxLen = Math.max(maxLen, w.length);
// canBreak[i] is true if s[i..n) can be split into dictionary words,
// and an empty suffix is always valid
const canBreak = new Array(n + 1).fill(false);
canBreak[n] = true;
for (let i = n - 1; i >= 0; i--) {
for (let end = i + 1; end <= Math.min(n, i + maxLen); end++) {
if (canBreak[end] && st.has(s.substring(i, end))) {
canBreak[i] = true;
break;
}
}
}
const res = [];
// no sentence is possible, so no split needs to be explored
if (!canBreak[0])
return res;
const path = [];
backtrack(s, 0, maxLen, st, canBreak, path, res);
return res;
}
// Driver Code
const dict = ["lik", "like", "egfg", "gfg"];
const s = "likegfg";
const res = wordBreak(dict, s);
res.sort();
console.log("[" + res.map(w => '"' + w + '"').join(", ") + "]");
Output
["lik egfg", "like gfg"]
Time Complexity: O(n * L2 + K * n * L2), where the DP checks n * L substrings and the backtracking makes O(K * n) calls, with every substring check taking O(L).
Auxiliary Space: O(n), for the canBreak[] array, the path list and the recursion stack, not counting the output.