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.
Table of Content
[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.
//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
//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
# 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
//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
// 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.
//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
//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
# 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
//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
// 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.
//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
//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
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
//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
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.
//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
//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
#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
//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
//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