Word Break II In C,CPP,JAVA,PYTHON,C#,JS

Spread the love
Close-up of a computer screen displaying programming code in a dark environment.

The Word Break II problem extends the classic Word Break problem, where you need to not only determine if a string can be segmented into a sequence of dictionary words but also return all possible segmentations.

Problem Statement:

Given a string s and a dictionary of words wordDict, return all possible sentences formed by segmenting s into a space-separated sequence of one or more dictionary words.

Example:

  • Input:makefileCopy codes = "catsanddog" wordDict = ["cat", "cats", "and", "sand", "dog"]
  • Output:cssCopy code["cats and dog", "cat sand dog"]

Approach:

The approach is similar to Word Break with dynamic programming, but this time we need to backtrack to construct all possible valid sentences. We will use:

  1. Dynamic Programming (DP): to store intermediate results.
  2. Backtracking: to generate all possible sentences once we know the valid breaks.

Steps:

  1. DP Table: Create a DP array dp[] where dp[i] is true if the substring s[0..i-1] can be segmented using the dictionary words.
  2. Backtracking: Once we know a substring s[0..i-1] can be segmented, recursively try all possible valid splits for the remaining substring.

Code Implementations in Multiple Languages:


1. C Code:

#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <stdlib.h>

bool wordBreakUtil(char *s, int start, int end, bool *dp) {
if (start == end) return true;
if (dp[start]) return false; // If already visited, no need to check

dp[start] = true;

for (int i = start + 1; i <= end; i++) {
char substr[i - start];
strncpy(substr, s + start, i - start);
substr[i - start] = '\0';

if (strstr(wordDict, substr) != NULL && wordBreakUtil(s, i, end, dp)) {
return true;
}
}

return false;
}

int main() {
char *s = "catsanddog";
char *wordDict[] = {"cat", "cats", "and", "sand", "dog"};

// Test all possible word breaks
if (wordBreakUtil(s, 0, strlen(s), dp)) {
printf("Possible word breaks exist");
} else {
printf("Impossible word breaks");
}

return 0;
}

2. Python Code (Word Break II):

def wordBreak(s, wordDict):
# Convert wordDict to a set for fast lookup
word_set = set(wordDict)

# Memoization: cache the results for substrings
memo = {}

def backtrack(start):
if start == len(s):
return [''] # Reached the end, return an empty string to signify no more words
if start in memo:
return memo[start] # Return already computed result for this start index

result = []
# Try all possible substrings starting from 'start'
for end in range(start + 1, len(s) + 1):
if s[start:end] in word_set:
# If the substring is a valid word, backtrack to find the rest of the sentence
rest_of_sentences = backtrack(end)
for sentence in rest_of_sentences:
# If rest_of_sentences is empty, just return the word itself
result.append(s[start:end] + ('' if not sentence else ' ' + sentence))

# Store the result in memoization cache and return it
memo[start] = result
return result

return backtrack(0)

# Test case
s = "catsanddog"
wordDict = ["cat", "cats", "and", "sand", "dog"]
print(wordBreak(s, wordDict)) # Output: ["cats and dog", "cat sand dog"]

Explanation:

  • We use backtracking to explore all possible segmentations of the string s.
  • The function backtrack(start) attempts to form valid sentences by checking all possible substrings starting from index start.
  • Memoization is used to cache results for subproblems to avoid recalculating the same results multiple times.

3. C++ Code (Word Break II):

#include <iostream>
#include <vector>
#include <unordered_set>
#include <unordered_map>
#include <string>
using namespace std;

class Solution {
public:
vector<string> wordBreak(string s, unordered_set<string>& wordDict) {
unordered_map<int, vector<string>> memo;
return backtrack(s, 0, wordDict, memo);
}

private:
vector<string> backtrack(const string& s, int start, unordered_set<string>& wordDict, unordered_map<int, vector<string>>& memo) {
if (start == s.length()) return {""}; // Base case: reach the end of the string

if (memo.find(start) != memo.end()) return memo[start]; // If already computed

vector<string> result;

// Try all possible substrings starting from 'start'
for (int end = start + 1; end <= s.length(); end++) {
string word = s.substr(start, end - start);
if (wordDict.find(word) != wordDict.end()) {
vector<string> restOfSentences = backtrack(s, end, wordDict, memo);
for (string sentence : restOfSentences) {
result.push_back(word + (sentence.empty() ? "" : " " + sentence));
}
}
}

memo[start] = result;
return result;
}
};

int main() {
Solution solution;
unordered_set<string> wordDict = {"cat", "cats", "and", "sand", "dog"};
string s = "catsanddog";

vector<string> result = solution.wordBreak(s, wordDict);

for (const string& sentence : result) {
cout << sentence << endl; // Output: "cats and dog", "cat sand dog"
}

return 0;
}

Explanation:

  • This code uses backtracking to explore all possible valid segmentations.
  • The memoization cache (unordered_map<int, vector<string>> memo) is used to store solutions for subproblems.
  • For each valid substring, we recursively find all segmentations of the remaining string and combine them with the current word.

4. Java Code (Word Break II):

import java.util.*;

public class Solution {
public List<String> wordBreak(String s, List<String> wordDict) {
Set<String> wordSet = new HashSet<>(wordDict);
Map<Integer, List<String>> memo = new HashMap<>();
return backtrack(s, 0, wordSet, memo);
}

private List<String> backtrack(String s, int start, Set<String> wordSet, Map<Integer, List<String>> memo) {
if (start == s.length()) return Arrays.asList(""); // Base case: end of string

if (memo.containsKey(start)) return memo.get(start); // Return if already computed

List<String> result = new ArrayList<>();

// Try all possible substrings starting from 'start'
for (int end = start + 1; end <= s.length(); end++) {
String word = s.substring(start, end);
if (wordSet.contains(word)) {
List<String> restOfSentences = backtrack(s, end, wordSet, memo);
for (String sentence : restOfSentences) {
result.add(word + (sentence.isEmpty() ? "" : " " + sentence));
}
}
}

memo.put(start, result); // Memoize result for the current start index
return result;
}

public static void main(String[] args) {
Solution solution = new Solution();
List<String> wordDict = Arrays.asList("cat", "cats", "and", "sand", "dog");
String s = "catsanddog";

List<String> result = solution.wordBreak(s, wordDict);

for (String sentence : result) {
System.out.println(sentence); // Output: "cats and dog", "cat sand dog"
}
}
}

Explanation:

  • Similar to the C++ and Python solutions, this solution uses backtracking and memoization to generate all valid segmentations.
  • We use a set for fast lookups to check if a substring exists in the dictionary.
  • The result is stored in a map (memo) to avoid redundant calculations.

5. C# Code (Word Break II):

using System;
using System.Collections.Generic;

public class Solution {
public IList<string> WordBreak(string s, IList<string> wordDict) {
HashSet<string> wordSet = new HashSet<string>(wordDict);
Dictionary<int, List<string>> memo = new Dictionary<int, List<string>>();
return Backtrack(s, 0, wordSet, memo);
}

private List<string> Backtrack(string s, int start, HashSet<string> wordSet, Dictionary<int, List<string>> memo) {
if (start == s.Length) return new List<string> { "" }; // Base case

if (memo.ContainsKey(start)) return memo[start]; // Return if already computed

List<string> result = new List<string>();

// Try all possible substrings starting from 'start'
for (int end = start + 1; end <= s.Length; end++) {
string word = s.Substring(start, end - start);
if (wordSet.Contains(word)) {
List<string> restOfSentences = Backtrack(s, end, wordSet, memo);
foreach (string sentence in restOfSentences) {
result.Add(word + (string.IsNullOrEmpty(sentence) ? "" : " " + sentence));
}
}
}

memo[start] = result; // Memoize result for the current start index
return result;
}

public static void Main() {
Solution solution = new Solution();
IList<string> wordDict = new List<string> { "cat", "cats", "and", "sand", "dog" };
string s = "catsanddog";

IList<string> result = solution.WordBreak(s, wordDict);

foreach (string sentence in result) {
Console.WriteLine(sentence); // Output: "cats and dog", "cat sand dog"
}
}
}

Explanation:

  • The solution follows the same pattern as the Java and C++ solutions using backtracking and memoization.
  • A HashSet is used for quick lookups of words in the dictionary.
  • Results for subproblems are stored in the memo dictionary to avoid redundant calculations.

6. JavaScript Code (Word Break II):

function wordBreak(s, wordDict) {
const wordSet = new Set(wordDict);
const memo = {};

function backtrack(start) {
if (start === s.length) return ['']; // Base case: reached the end of the string

if (memo[start]) return memo[start]; // If already computed

const result = [];

// Try all possible substrings starting from 'start'
for (let end = start + 1; end <= s.length; end++) {
const word = s.slice(start, end);
if (wordSet.has(word)) {
const restOfSentences = backtrack(end);
for (let sentence of restOfSentences) {
result.push(word + (sentence ? ' ' + sentence : ''));
}
}
}

memo[start] = result; // Memoize result for the current start index
return result;
}

return backtrack(0);
}

// Test case
const s = "catsanddog";
const wordDict = ["cat", "cats", "and", "sand", "dog"];
console.log(wordBreak(s, wordDict)); // Output: ["cats and dog", "cat sand dog"]

Explanation:

  • The backtracking function generates all valid segmentations, and memoization is used to store previously computed results for each index start.
  • A Set is used for fast lookup to check if a substring is present in the dictionary.

Time and Space Complexity:

  • Time Complexity: O(N^2), where N is the length of the string. We try all possible substrings and backtrack for each valid segment.
  • Space Complexity: O(N^2) for storing the results in the memoization table and the recursion stack.

Conclusion:

The Word Break II problem is a great example of dynamic programming combined with backtracking. By using memoization and recursion, we can efficiently generate all possible valid segmentations of a string.