Minimum Word Break

Last Updated : 20 Nov, 2025

Given a string s and a dictionary of words dict[], partition s into substrings such that every substring is present in dict[]. Find the minimum number of cuts required to achieve this.

  • A cut is made between two characters to split the string into valid dictionary words.
  • If the string cannot be fully partitioned using dict[], return -1.

Examples: 

Input: s = "pineapplepen", dict[] = ["pine", "apple", "pen", "pineapple"]
Output: 1
Explanation: We can partition as "pineapple | pen" - 1 cut.

Input: s = "abcxyz", dict[] = ["ab", "bc", "xy", "yz"]
Output: -1
Explanation: No way to partition "abcxyz" entirely using the dictionary words.

[Naive Approach] Using Recursion

The idea is to try breaking the string at every possible index and check whether the left part is a valid word from the dictionary. If it is valid, we recursively solve the remaining right part in the same manner. For every valid partitioning, we compute the total number of segments formed; the number of breaks for that partition is therefore segments − 1. We take the minimum number of breaks among all valid ways to split the string.

C++
//Driver Code Starts
#include <iostream>
#include <vector>
#include <string>
#include <climits>
using namespace std;

//Driver Code Ends

// check if a substring exists in the dictionary
bool isword(string &s, vector<string> &dict) {
    for (auto &w : dict) {
        if (w == s) return true;
    }
    return false;
}

// recursively try all possible break positions
int recMinBreaks(string &s, int idx, vector<string> &dict) {

    // reached end no more breaks needed
    if (idx == (int)s.size()) return 0;

    int ans = INT_MAX;
    string temp = "";

    // build prefix character by character
    for (int i = idx; i < (int)s.size(); i++) {
        
        // extend current prefix
        temp.push_back(s[i]);   

        // prefix matches dictionary to solve remaining part
        if (isword(temp, dict)) {
            int right = recMinBreaks(s, i + 1, dict);

            if (right != INT_MAX)
                ans = min(ans, 1 + right);
        }
    }

    return ans;
}

// function to find min breaks required
int minbreaks(string &s, vector<string> &dict) {
    int res = recMinBreaks(s, 0, dict);
    return (res == INT_MAX ? -1 : res - 1);
}

//Driver Code Starts

int main() {

    string s = "pineapplepen";
    vector<string> dict = {"pine", "apple", "pen", "pineapple"};

    cout << minbreaks(s, dict);

    return 0;
}

//Driver Code Ends
Java
//Driver Code Starts
class GFG {

//Driver Code Ends

    // check if a substring exists in the dictionary
    static boolean isword(String s, String[] dict) {
        for (String w : dict) {
            if (w.equals(s)) return true;
        }
        return false;
    }

    // recursively try all possible break positions
    static int recMinBreaks(String s, int idx, String[] dict) {

        // reached end no more breaks needed
        if (idx == s.length()) return 0;

        int ans = Integer.MAX_VALUE;
        StringBuilder temp = new StringBuilder();

        // build prefix character by character
        for (int i = idx; i < s.length(); i++) {
            
            // extend current prefix
            temp.append(s.charAt(i));  

            // prefix matches dictionary to solve remaining part
            if (isword(temp.toString(), dict)) {
                int right = recMinBreaks(s, i + 1, dict);

                if (right != Integer.MAX_VALUE)
                    ans = Math.min(ans, 1 + right);
            }
        }

        return ans;
    }

    // function to find min breaks required
    static int minbreaks(String s, String[] dict) {
        int res = recMinBreaks(s, 0, dict);
        return (res == Integer.MAX_VALUE) ? -1 : res - 1;
    }

//Driver Code Starts

    public static void main(String[] args) {

        String s = "pineapplepen";
        String[] dict = {"pine", "apple", "pen", "pineapple"};

        System.out.println(minbreaks(s, dict));
    }
}

//Driver Code Ends
Python
# check if a substring exists in the dictionary
def isword(s, dict):
    for w in dict:
        if w == s:
            return True
    return False

# recursively try all possible break positions
def recMinBreaks(s, idx, dict):

    # reached end no more breaks needed
    if idx == len(s):
        return 0

    ans = float('inf')
    temp = ""

    # build prefix character by character
    for i in range(idx, len(s)):
        
        # extend prefix
        temp += s[i]  

        # prefix matches dictionary to solve remaining part
        if isword(temp, dict):
            right = recMinBreaks(s, i + 1, dict)

            if right != float('inf'):
                ans = min(ans, 1 + right)

    return ans

# function to find min breaks required
def minbreaks(s, dict):
    res = recMinBreaks(s, 0, dict)
    return -1 if res == float('inf') else res - 1


#Driver Code Starts
if __name__ == '__main__':
    s = "pineapplepen"
    dict = ["pine", "apple", "pen", "pineapple"]
    
    print(minbreaks(s, dict))

#Driver Code Ends
C#
//Driver Code Starts
using System;

class GFG {

//Driver Code Ends

    // check if a substring exists in the dictionary
    static bool isword(string s, string[] dict) {
        foreach (var w in dict) {
            if (w == s) return true;
        }
        return false;
    }

    // recursively try all possible break positions
    static int recMinBreaks(string s, int idx, string[] dict) {

        // reached end no more breaks needed
        if (idx == s.Length) return 0;

        int ans = int.MaxValue;
        string temp = "";

        // build prefix character by character
        for (int i = idx; i < s.Length; i++) {
            
            // extend current prefix
            temp += s[i];  

            if (isword(temp, dict)) {
                int right = recMinBreaks(s, i + 1, dict);

                if (right != int.MaxValue)
                    ans = Math.Min(ans, 1 + right);
            }
        }

        return ans;
    }

    // function to find min breaks required
    static int minbreaks(string s, string[] dict) {
        int res = recMinBreaks(s, 0, dict);
        return (res == int.MaxValue) ? -1 : res - 1;
    }

//Driver Code Starts

    static void Main() {

        string s = "pineapplepen";
        string[] dict = {"pine", "apple", "pen", "pineapple"};

        Console.WriteLine(minbreaks(s, dict));
    }
}

//Driver Code Ends
JavaScript
// check if a substring exists in the dictionary
function isword(s, dict) {
    for (let w of dict) {
        if (w === s) return true;
    }
    return false;
}

// recursively try all possible break positions
function recMinBreaks(s, idx, dict) {

    // reached end no more breaks needed
    if (idx === s.length) return 0;

    let ans = Infinity;
    let temp = "";

    // build prefix character by character
    for (let i = idx; i < s.length; i++) {
        
        // extend current prefix
        temp += s[i];   

        // prefix matches dictionary to solve remaining part
        if (isword(temp, dict)) {
            let right = recMinBreaks(s, i + 1, dict);

            if (right !== Infinity)
                ans = Math.min(ans, 1 + right);
        }
    }

    return ans;
}

// function to find min breaks required
function minbreaks(s, dict) {
    let res = recMinBreaks(s, 0, dict);
    return res === Infinity ? -1 : res - 1;
}


//Driver Code Starts
// Driver code
let s = "pineapplepen";
let dict = ["pine", "apple", "pen", "pineapple"];

console.log(minbreaks(s, dict));

//Driver Code Ends

Output
1

Time Complexity: O(2n * m * k), where n is length of string s and m is number of words in the dictionary and k is average word length in the dictionary.
At each index, we can either extend the current word or break it, giving 2ⁿ possibilities. Each break checks the formed string against m dictionary words of average length k.
Auxiliary Space: O(n2), because the recursion can go n levels deep, and at each level the temporary substring can be as long as O(n), so all those partial strings together occupy n + (n-1) + … + 1 = O(n²) space.

[Better Approach 1] Using Memoization

The idea is to avoid recomputing results for the same substring again and again. We maintain a DP array dp[idx] that stores the minimum number of segments needed to split the substring starting from index idx.

At any moment, dp[idx] represents the best segmentation count for the suffix beginning at that position. During recursion, when we reach an index, we first check if dp[idx] already holds a value; if yes, we simply return it. Otherwise, we compute the value for that index, store it in dp[idx], and return it.

The required number of breaks from index idx to n is dp[idx] − 1, because dp[idx] stores the number of segments, and the number of breaks is always one less than the number of segments.

C++
//Driver Code Starts
#include <iostream>
#include <vector>
#include <string>
#include <climits>
using namespace std;

//Driver Code Ends

// check if a substring exists in the dictionary
bool isword(string &s, vector<string> &dict) {
    for (auto &w : dict) {
        if (w == s) return true;
    }
    return false;
}

// recursive function with memoization
int recMinBreaks(string &s, int idx, vector<string> &dict,
                                            vector<int> &dp) {

    // reached end
    if (idx == (int)s.size()) return 0;

    // already computed
    if (dp[idx] != -1) return dp[idx];

    int ans = INT_MAX;
    string temp = "";

    // build prefix char by char
    for (int i = idx; i < (int)s.size(); i++) {
        temp.push_back(s[i]);

        // check prefix and solve remaining
        if (isword(temp, dict)) {
            int right = recMinBreaks(s, i + 1, dict, dp);
            if (right != INT_MAX)
                ans = min(ans, 1 + right);
        }
    }

    return dp[idx] = ans;
}

// function to find min breaks required
int minbreaks(string &s, vector<string> &dict) {
    vector<int> dp(s.size(), -1);
    int res = recMinBreaks(s, 0, dict, dp);
    return (res == INT_MAX ? -1 : res - 1);
}

//Driver Code Starts

int main() {

    string s = "pineapplepen";
    vector<string> dict = {"pine", "apple", "pen", "pineapple"};

    cout << minbreaks(s, dict);

    return 0;
}

//Driver Code Ends
Java
//Driver Code Starts
import java.util.Arrays;

class GFG {

//Driver Code Ends

    // check if a substring exists in the dictionary
    static boolean isword(String s, String[] dict) {
        for (String w : dict) {
            if (w.equals(s)) return true;
        }
        return false;
    }

    // recursively try all possible break positions
    static int recminbreaks(String s, int idx, String[] dict, int[] dp) {

        // reached end no more breaks needed
        if (idx == s.length()) return 0;

        // already computed
        if (dp[idx] != -1) return dp[idx];

        int ans = Integer.MAX_VALUE;
        String temp = "";

        // build prefix character by character
        for (int i = idx; i < s.length(); i++) {

            // extend current prefix
            temp += s.charAt(i);

            // prefix matches dictionary to solve remaining part
            if (isword(temp, dict)) {
                int right = recminbreaks(s, i + 1, dict, dp);

                if (right != Integer.MAX_VALUE)
                    ans = Math.min(ans, 1 + right);
            }
        }

        return dp[idx] = ans;
    }

    // function to find min breaks required
    static int minbreaks(String s, String[] dict) {
        int[] dp = new int[s.length()];
        Arrays.fill(dp, -1);

        int res = recminbreaks(s, 0, dict, dp);
        return (res == Integer.MAX_VALUE ? -1 : res - 1);
    }

//Driver Code Starts

    public static void main(String[] args) {

        String s = "pineapplepen";
        String[] dict = {"pine", "apple", "pen", "pineapple"};

        System.out.println(minbreaks(s, dict));
    }
}

//Driver Code Ends
Python
# check if a substring exists in the dictionary
def isword(s, dict):
    for w in dict:
        if w == s:
            return True
    return False

# recursively try all possible break positions
def recminbreaks(s, idx, dict, dp):

    # reached end no more breaks needed
    if idx == len(s):
        return 0

    # already computed
    if dp[idx] != -1:
        return dp[idx]

    ans = float('inf')
    temp = ""

    # build prefix character by character
    for i in range(idx, len(s)):

        # extend current prefix
        temp += s[i]

        # prefix matches dictionary to solve remaining part
        if isword(temp, dict):
            right = recminbreaks(s, i + 1, dict, dp)

            if right != float('inf'):
                ans = min(ans, 1 + right)

    dp[idx] = ans
    return ans

# function to find min breaks required
def minbreaks(s, dict):
    dp = [-1] * len(s)
    res = recminbreaks(s, 0, dict, dp)
    return -1 if res == float('inf') else res - 1


#Driver Code Starts
if __name__ == '__main__':
    s = "pineapplepen"
    dict = ["pine", "apple", "pen", "pineapple"]
    
    print(minbreaks(s, dict))

#Driver Code Ends
C#
//Driver Code Starts
using System;

class GFG {
    
//Driver Code Ends

    // check if a substring exists in the dictionary
    static bool isword(string s, string[] dict) {
        foreach (var w in dict)
        {
            if (w == s) return true;
        }
        return false;
    }

    // recursively try all possible break positions
    static int recminbreaks(string s, int idx, string[] dict, int[] dp) {
        
        // reached end no more breaks needed
        if (idx == s.Length) return 0;

        // already computed
        if (dp[idx] != -1) return dp[idx];

        int ans = int.MaxValue;
        string temp = "";

        // build prefix character by character
        for (int i = idx; i < s.Length; i++)
        {
            // extend current prefix
            temp += s[i];

            // prefix matches dictionary to solve remaining part
            if (isword(temp, dict))
            {
                int right = recminbreaks(s, i + 1, dict, dp);

                if (right != int.MaxValue)
                    ans = Math.Min(ans, 1 + right);
            }
        }

        return dp[idx] = ans;
    }

    // function to find min breaks required
    static int minbreaks(string s, string[] dict)
    {
        int[] dp = new int[s.Length];
        for (int i = 0; i < dp.Length; i++) dp[i] = -1;

        int res = recminbreaks(s, 0, dict, dp);
        return (res == int.MaxValue ? -1 : res - 1);
    }

//Driver Code Starts

    static void Main()
    {
        string s = "pineapplepen";
        string[] dict = { "pine", "apple", "pen", "pineapple" };

        Console.WriteLine(minbreaks(s, dict));
    }
}

//Driver Code Ends
JavaScript
// check if a substring exists in the dictionary
function isword(s, dict) {
    for (let w of dict) {
        if (w === s) return true;
    }
    return false;
}

// recursively try all possible break positions
function recminbreaks(s, idx, dict, dp) {

    // reached end no more breaks needed
    if (idx === s.length) return 0;

    // already computed
    if (dp[idx] !== -1) return dp[idx];

    let ans = Infinity;
    let temp = "";

    // build prefix character by character
    for (let i = idx; i < s.length; i++) {

        // extend current prefix
        temp += s[i];

        // prefix matches dictionary to solve remaining part
        if (isword(temp, dict)) {
            let right = recminbreaks(s, i + 1, dict, dp);

            if (right !== Infinity)
                ans = Math.min(ans, 1 + right);
        }
    }

    return dp[idx] = ans;
}

// function to find min breaks required
function minbreaks(s, dict) {
    let dp = new Array(s.length).fill(-1);
    let res = recminbreaks(s, 0, dict, dp);
    return res === Infinity ? -1 : res - 1;
}


//Driver Code Starts
// Driver Code
let s = "pineapplepen";
let dict = ["pine", "apple", "pen", "pineapple"];

console.log(minbreaks(s, dict));

//Driver Code Ends

Output
1

Time Complexity: O(n2 * m * k),  where n is the length of string s, m is the number of words in the dictionary, and k is the average word length in the dictionary.
At each index, we try all possible substrings from current position to end (up to n2 substrings). For each substring formed, we check against m dictionary words, with each comparison taking O(k) on average.
Auxiliary Space: O(n2), because the recursion can go n levels deep, and at each level the temporary substring can be as long as O(n), so all those partial strings together occupy n + (n-1) + … + 1 = O(n²) space.

[Better Approach 2] Using Tabulation

We build a dp array of size n+1, where:

  • dp[i] = minimum number of segments formed from the substring s[i…n−1]
  • dp[n] = 0 (since an empty suffix requires zero words)

We fill the dp array from right to left. For each index i, we try every possible substring s[i…j] (i ≤ j < n) and check if it exists in the dictionary. If it is a valid word, then:

dp[i] = min(dp[i], 1 + dp[j+1])

Meaning:

  • choose the word s[i…j]: contributes 1 segment
  • add the optimal segments starting from j+1

At the end, dp[0] gives the minimum number of segments for the entire string. Since breaks = segments − 1, the final answer = dp[0] − 1.

C++
//Driver Code Starts
#include <iostream>
#include <vector>
#include <string>
#include <unordered_set>
#include <climits>
using namespace std;

//Driver Code Ends

int minbreaks(string &s, vector<string> &dict) {

    int n = s.size();

    // dp[i] = minimum breaks needed for suffix starting at i
    vector<int> dp(n + 1, INT_MAX);

    // empty suffix needs 0 breaks
    dp[n] = 0;

    // store dictionary words for quick lookup
    unordered_set<string> st(dict.begin(), dict.end());

    // fill dp from right to left
    for (int i = n - 1; i >= 0; --i) {

        string temp = "";

        // try substrings s[i..j]
        for (int j = i; j < n; ++j) {

            temp.push_back(s[j]);

            // if substring matches a dictionary word
            if (st.count(temp)) {

                if (dp[j + 1] != INT_MAX) {
                    dp[i] = min(dp[i], 1 + dp[j + 1]);
                }
            }
        }
    }

    // remove extra break from the last word
    if (dp[0] == INT_MAX) return -1;
    
    return dp[0] - 1;
}

//Driver Code Starts

int main() {

    string s = "pineapplepen";
    vector<string> dict = {"pine", "apple", "pen", "pineapple"};

    int ans = minbreaks(s, dict);
    cout << ans;

    return 0;
}

//Driver Code Ends
Java
//Driver Code Starts
import java.util.Arrays;

class GFG {

//Driver Code Ends

    static int minbreaks(String s, String[] dict) {

        int n = s.length();

        // dp[i] = minimum breaks needed for suffix starting at i
        int[] dp = new int[n + 1];
        Arrays.fill(dp, Integer.MAX_VALUE);

        // empty suffix needs 0 breaks
        dp[n] = 0;

        // store dictionary words for quick lookup
        HashSet<String> st = new HashSet<>();
        for (String w : dict) st.add(w);

        // fill dp from right to left
        for (int i = n - 1; i >= 0; --i) {

            StringBuilder temp = new StringBuilder();

            // try substrings s[i..j]
            for (int j = i; j < n; ++j) {

                temp.append(s.charAt(j));

                // if substring matches a dictionary word
                if (st.contains(temp.toString())) {

                    if (dp[j + 1] != Integer.MAX_VALUE) {
                        dp[i] = Math.min(dp[i], 1 + dp[j + 1]);
                    }
                }
            }
        }

        // remove extra break from the last word
        if (dp[0] == Integer.MAX_VALUE) return -1;
        return dp[0] - 1;
    }

//Driver Code Starts

    public static void main(String[] args) {

        String s = "pineapplepen";
        String[] dict = {"pine", "apple", "pen", "pineapple"};

        int ans = minbreaks(s, dict);
        System.out.println(ans);
    }
}

//Driver Code Ends
Python
def minbreaks(s, dict):

    n = len(s)

    # dp[i] = minimum breaks needed for suffix starting at i
    dp = [float('inf')] * (n + 1)

    # empty suffix needs 0 breaks
    dp[n] = 0

    # store dictionary words in a set
    st = set(dict)

    # fill dp from right to left
    for i in range(n - 1, -1, -1):

        temp = ""

        # try substrings s[i..j]
        for j in range(i, n):

            temp += s[j]

            # if substring matches a dictionary word
            if temp in st:

                if dp[j + 1] != float('inf'):
                    dp[i] = min(dp[i], 1 + dp[j + 1])

    # remove extra break from the last word
    if dp[0] == float('inf'):
        return -1
    return dp[0] - 1


#Driver Code Starts

if __name__ == '__main__':
    s = "pineapplepen"
    dict = ["pine", "apple", "pen", "pineapple"]
    
    print(minbreaks(s, dict))

#Driver Code Ends
C#
//Driver Code Starts
using System;
using System.Collections.Generic;

class GFG {

//Driver Code Ends

    static int minbreaks(string s, string[] dict) {

        int n = s.Length;

        // dp[i] = minimum breaks needed for suffix starting at i
        int[] dp = new int[n + 1];
        for (int i = 0; i <= n; i++) dp[i] = int.MaxValue;

        // empty suffix needs 0 breaks
        dp[n] = 0;

        // store dictionary words for quick lookup
        HashSet<string> st = new HashSet<string>(dict);

        // fill dp from right to left
        for (int i = n - 1; i >= 0; --i) {

            string temp = "";

            // try substrings s[i..j]
            for (int j = i; j < n; ++j) {

                temp += s[j];

                // if substring matches a dictionary word
                if (st.Contains(temp)) {

                    if (dp[j + 1] != int.MaxValue) {
                        dp[i] = Math.Min(dp[i], 1 + dp[j + 1]);
                    }
                }
            }
        }

        // remove extra break from the last word
        if (dp[0] == int.MaxValue) return -1;
        return dp[0] - 1;
    }

//Driver Code Starts

    static void Main() {

        string s = "pineapplepen";
        string[] dict = {"pine", "apple", "pen", "pineapple"};

        int ans = minbreaks(s, dict);
        Console.WriteLine(ans);
    }
}

//Driver Code Ends
JavaScript
function minbreaks(s, dict) {

    let n = s.length;

    // dp[i] = minimum breaks needed for suffix starting at i
    let dp = new Array(n + 1).fill(Number.MAX_SAFE_INTEGER);

    // empty suffix needs 0 breaks
    dp[n] = 0;

    // convert dictionary into set
    let st = new Set(dict);

    // fill dp from right to left
    for (let i = n - 1; i >= 0; --i) {

        let temp = "";

        // try substrings s[i..j]
        for (let j = i; j < n; ++j) {

            temp += s[j];

            // if substring matches a dictionary word
            if (st.has(temp)) {

                if (dp[j + 1] !== Number.MAX_SAFE_INTEGER) {
                    dp[i] = Math.min(dp[i], 1 + dp[j + 1]);
                }
            }
        }
    }

    // remove extra break from the last word
    if (dp[0] === Number.MAX_SAFE_INTEGER) return -1;
    return dp[0] - 1;
}


//Driver Code Starts
// Driver Code
let s = "pineapplepen";
let dict = ["pine", "apple", "pen", "pineapple"];

console.log(minbreaks(s, dict));

//Driver Code Ends

Output
1

Time Complexity: O(n² × m × k), where n is the length of string s, m is the number of words in the dictionary, and k is the average word length.
For each index i, we try all possible substrings s[i…j] (up to n substrings). For each substring, we check against m dictionary words, with each comparison taking O(k) on average.
Auxiliary Space: O(n + m * k), where n is the string length (dp array) and m × k is for storing dictionary words.

[Expected Approach] Using Trie and DP

We can combine the tabulation approach with a Trie to optimize dictionary lookups. Instead of checking every substring against all dictionary words, we build a Trie from the dictionary. Then, while filling the dp array from right to left, we walk the Trie character by character from index i to find valid words.

Whenever a Trie node marks the end of a word, we update:

dp[i] = min(dp[i], 1 + dp[j+1])

This way, substring checks are done by traversing the Trie, avoiding repeated comparisons with every dictionary word.

C++
//Driver Code Starts
#include <iostream>
#include <vector>
#include <string>
#include <climits>
using namespace std;

// Trie node definition
struct TrieNode {
    TrieNode* child[26];
    bool isWord;
    TrieNode() {
        for (int i = 0; i < 26; i++) child[i] = nullptr;
        isWord = false;
    }
};

// Insert word into Trie
void insertWord(TrieNode* root, const string &word) {
    TrieNode* curr = root;
    for (char c : word) {
        int idx = c - 'a';
        if (!curr->child[idx]) curr->child[idx] = new TrieNode();
        curr = curr->child[idx];
    }
    curr->isWord = true;
}

//Driver Code Ends

int minbreaks(string &s, vector<string> &dict) {
    int n = s.size();
    vector<int> dp(n + 1, INT_MAX);
    
    // empty suffix requires 0 words
    dp[n] = 0; 

    // build Trie inside the function
    TrieNode* root = new TrieNode();
    for (auto &word : dict) insertWord(root, word);

    for (int i = n - 1; i >= 0; i--) {
        TrieNode* curr = root;
        for (int j = i; j < n; j++) {
            int idx = s[j] - 'a';
            
            // no word starting with this prefix
            if (!curr->child[idx]) break; 
            curr = curr->child[idx];
            if (curr->isWord && dp[j + 1] != INT_MAX) {
                dp[i] = min(dp[i], 1 + dp[j + 1]);
            }
        }
    }
    
    // breaks = words - 1
    return (dp[0] == INT_MAX ? -1 : dp[0] - 1); 
}

//Driver Code Starts

int main() {
    string s = "pineapplepen";
    vector<string> dict = {"pine", "apple", "pen", "pineapple"};

    cout << minbreaks(s, dict);

    return 0;
}

//Driver Code Ends
Java
//Driver Code Starts
import java.util.Arrays;

class TrieNode {
    TrieNode[] child = new TrieNode[26];
    boolean isWord = false;
}

class GFG {

    // Insert word into Trie
    static void insertWord(TrieNode root, String word) {
        TrieNode curr = root;
        for (char c : word.toCharArray()) {
            int idx = c - 'a';
            if (curr.child[idx] == null) curr.child[idx] = new TrieNode();
            curr = curr.child[idx];
        }
        curr.isWord = true;
    }

//Driver Code Ends

    static int minbreaks(String s, String[] dict) {
        int n = s.length();
        int[] dp = new int[n + 1];
        Arrays.fill(dp, Integer.MAX_VALUE);

        // empty suffix requires 0 words
        dp[n] = 0;

        // build Trie inside the function
        TrieNode root = new TrieNode();
        for (String word : dict) insertWord(root, word);

        // fill dp from right to left
        for (int i = n - 1; i >= 0; i--) {
            TrieNode curr = root;
            for (int j = i; j < n; j++) {
                
                int idx = s.charAt(j) - 'a';
                
                // no word starting with this prefix
                if (curr.child[idx] == null) break;
                curr = curr.child[idx];

                // substring matches a word in Trie
                if (curr.isWord && dp[j + 1] != Integer.MAX_VALUE) {
                    dp[i] = Math.min(dp[i], 1 + dp[j + 1]);
                }
            }
        }

        // breaks = words - 1
        return dp[0] == Integer.MAX_VALUE ? -1 : dp[0] - 1;
    }

//Driver Code Starts

    public static void main(String[] args) {
        String s = "pineapplepen";
        String[] dict = {"pine", "apple", "pen", "pineapple"};

        System.out.println(minbreaks(s, dict));
    }
}

//Driver Code Ends
Python
#Driver Code Starts
class TrieNode:
    def __init__(self):
        self.child = [None]*26
        self.isWord = False

# Insert word into Trie
def insertWord(root, word):
    curr = root
    for c in word:
        idx = ord(c) - ord('a')
        if not curr.child[idx]:
            curr.child[idx] = TrieNode()
        curr = curr.child[idx]
    curr.isWord = True

#Driver Code Ends

def minbreaks(s, dict):
    n = len(s)
    dp = [float('inf')] * (n + 1)

    # empty suffix requires 0 words
    dp[n] = 0

    # build Trie inside the function
    root = TrieNode()
    for word in dict:
        insertWord(root, word)

    # fill dp from right to left
    for i in range(n-1, -1, -1):
        curr = root
        for j in range(i, n):
            idx = ord(s[j]) - ord('a')

            # no word starting with this prefix
            if not curr.child[idx]:
                break
            curr = curr.child[idx]

            # substring matches a word in Trie
            if curr.isWord and dp[j + 1] != float('inf'):
                dp[i] = min(dp[i], 1 + dp[j + 1])

    # breaks = words - 1
    return -1 if dp[0] == float('inf') else dp[0] - 1

#Driver Code Starts

if __name__ == '__main__':
    s = "pineapplepen"
    dict = ["pine", "apple", "pen", "pineapple"]
    print(minbreaks(s, dict))

#Driver Code Ends
C#
//Driver Code Starts
using System;
using System.Collections.Generic;

class TrieNode {
    public TrieNode[] child = new TrieNode[26];
    public bool isWord = false;
}

class GFG {

    // Insert word into Trie
    static void insertWord(TrieNode root, string word) {
        TrieNode curr = root;
        foreach (char c in word) {
            int idx = c - 'a';
            if (curr.child[idx] == null) curr.child[idx] = new TrieNode();
            curr = curr.child[idx];
        }
        curr.isWord = true;
    }

//Driver Code Ends

    static int minBreaks(string s, string[] dict) {
        int n = s.Length;
        int[] dp = new int[n + 1];
        for (int i = 0; i <= n; i++) dp[i] = int.MaxValue;

        // empty suffix requires 0 words
        dp[n] = 0;

        // build Trie inside the function
        TrieNode root = new TrieNode();
        foreach (string word in dict) insertWord(root, word);

        // fill dp from right to left
        for (int i = n - 1; i >= 0; i--) {
            TrieNode curr = root;
            for (int j = i; j < n; j++) {
                int idx = s[j] - 'a';

                // no word starting with this prefix
                if (curr.child[idx] == null) break;
                curr = curr.child[idx];

                // substring matches a word in Trie
                if (curr.isWord && dp[j + 1] != int.MaxValue) {
                    dp[i] = Math.Min(dp[i], 1 + dp[j + 1]);
                }
            }
        }

        // breaks = words - 1
        return dp[0] == int.MaxValue ? -1 : dp[0] - 1;
    }

//Driver Code Starts

    static void Main() {
        string s = "pineapplepen";
        string[] dict = { "pine", "apple", "pen", "pineapple" };

        Console.WriteLine(minBreaks(s, dict));
    }
}

//Driver Code Ends
JavaScript
//Driver Code Starts
// Trie node definition
class TrieNode {
    constructor() {
        this.child = Array(26).fill(null);
        this.isWord = false;
    }
}

// Insert word into Trie
function insertWord(root, word) {
    let curr = root;
    for (let c of word) {
        let idx = c.charCodeAt(0) - 'a'.charCodeAt(0);
        if (!curr.child[idx]) curr.child[idx] = new TrieNode();
        curr = curr.child[idx];
    }
    curr.isWord = true;
}

//Driver Code Ends

function minbreaks(s, dict) {
    const n = s.length;
    const dp = Array(n + 1).fill(Infinity);

    // empty suffix requires 0 words
    dp[n] = 0;

    // build Trie inside the function
    const root = new TrieNode();
    for (let word of dict) insertWord(root, word);

    // fill dp from right to left
    for (let i = n - 1; i >= 0; i--) {
        let curr = root;
        for (let j = i; j < n; j++) {
            let idx = s[j].charCodeAt(0) - 'a'.charCodeAt(0);

            // no word starting with this prefix
            if (!curr.child[idx]) break;
            curr = curr.child[idx];

            // substring matches a word in Trie
            if (curr.isWord && dp[j + 1] !== Infinity) {
                dp[i] = Math.min(dp[i], 1 + dp[j + 1]);
            }
        }
    }

    // breaks = words - 1
    return dp[0] === Infinity ? -1 : dp[0] - 1;
}

//Driver Code Starts

// Driver code
let s = "pineapplepen";
let dict = ["pine", "apple", "pen", "pineapple"];
console.log(minbreaks(s, dict));

//Driver Code Ends

Output
1

Time Complexity: O(m × k + n²), where n is the length of string s, m is the number of words in the dictionary, and k is the average word length. Building the Trie takes O(m × k), and filling the DP array involves traversing each suffix while following Trie paths, which in the worst case sums to O(n²)
Auxiliary Space: O(m × k + n), as the Trie stores all dictionary words and the DP array requires O(n) space


Comment