Word Break - Find All Combinations

Last Updated : 5 Oct, 2026

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.

Try It Yourself
redirect icon

[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.
C++
#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;
}
Java
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);
    }
}
Python
# 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) + "]")
C#
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);
    }
}
JavaScript
// 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).
C++
#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;
}
Java
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);
    }
}
Python
# 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) + "]")
C#
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);
    }
}
JavaScript
// 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].


C++
#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;
}
Java
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);
    }
}
Python
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) + "]")
C#
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);
    }
}
JavaScript
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.
C++
#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;
}
Java
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);
    }
}
Python
# 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) + "]")
C#
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);
    }
}
JavaScript
// 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.

Comment