Java - Word Break Problem

Problem Statement

Given a string s and a dictionary of words wordDict, determine if s can be segmented into a sequence of one or more dictionary words.

Example 1

Input: s = "leetcode", wordDict = ["leet", "code"]

Output: true

Explanation: "leetcode" can be segmented as "leet code".

Example 2

Input: s = "applepenapple", wordDict = ["apple", "pen"]

Output: true

Explanation: "applepenapple" can be segmented as "apple pen apple".

Example 3

Input: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"]

Output: false

Explanation: "catsandog" cannot be segmented into words from the dictionary.

Approach 1: Dynamic Programming (Bottom-Up)

Use a boolean DP array (dp[i]) where dp[i] = true means the substring s[0...i] can be segmented.

Iterate over s and check all possible prefixes that exist in wordDict.

If dp[j] is true and s[j...i] exists in wordDict, set dp[i] = true.

import java.util.*;

public class WordBreak {

    public static boolean wordBreak(String s, List wordDict) {

        Set wordSet = new HashSet<>(wordDict); // Convert list to set for O(1) lookup

        int n = s.length();

        boolean[] dp = new boolean[n + 1]; // DP array

        dp[0] = true; // Empty string is always valid

        for (int i = 1; i <= n; i++) {

            for (int j = 0; j < i; j++) {

                if (dp[j] && wordSet.contains(s.substring(j, i))) {

                    dp[i] = true;

                    break; // No need to check further if we found a valid partition

                }

            }

        }

        return dp[n]; // Final result

    }

    public static void main(String[] args) {

        String s = "leetcode";

        List wordDict = Arrays.asList("leet", "code");

        System.out.println("Can the word be segmented? " + wordBreak(s, wordDict)); // true

    }

}

Approach 2: Recursion + Memoization (Top-Down)

Try to break the string recursively at every possible prefix.

Use memoization (Map<Integer, Boolean> memo) to avoid recomputation.

If any valid partition is found, return true.

import java.util.*;

public class WordBreakRecursive {

    private static Map<Integer, Boolean> memo = new HashMap<>();

    public static boolean wordBreak(String s, List wordDict) {

        Set wordSet = new HashSet<>(wordDict);

        return helper(s, wordSet, 0);

    }

    private static boolean helper(String s, Set wordSet, int start) {

        if (start == s.length()) return true; // Successfully segmented

        if (memo.containsKey(start)) return memo.get(start); // Return memoized result

        for (int end = start + 1; end <= s.length(); end++) {

            if (wordSet.contains(s.substring(start, end)) && helper(s, wordSet, end)) {

                memo.put(start, true);

                return true;

            }

        }

        memo.put(start, false);

        return false;

    }

    public static void main(String[] args) {

        String s = "applepenapple";

        List wordDict = Arrays.asList("apple", "pen");

        System.out.println("Can the word be segmented? " + wordBreak(s, wordDict)); // true

    }

}

Complexity Analysis

Approach Time Complexity Space Complexity

DP (Bottom-Up) O(N^2) O(N)

Recursion + Memoization O(N^2) O(N)

Dynamic Programming is the most efficient and recommended approach.

Recursion is useful for understanding the problem but may cause a stack overflow for large inputs.

Extensions: Printing All Possible Segmentations

Instead of returning true/false, we can print all possible segmentations of the word.

import java.util.*;

public class WordBreakAll {

    public static List wordBreak(String s, List wordDict) {

        Set wordSet = new HashSet<>(wordDict);

        List result = new ArrayList<>();

        backtrack(s, wordSet, 0, "", result);

        return result;

    }

    private static void backtrack(String s, Set wordSet, int start, String current, List result) {

        if (start == s.length()) {

            result.add(current.trim()); // Remove leading space

            return;

        }

        for (int end = start + 1; end <= s.length(); end++) {

            String prefix = s.substring(start, end);

            if (wordSet.contains(prefix)) {

                backtrack(s, wordSet, end, current + " " + prefix, result);

            }

        }

    }

    public static void main(String[] args) {

        String s = "catsanddog";

        List wordDict = Arrays.asList("cat", "cats", "and", "sand", "dog");

        System.out.println("Possible Segmentations: " + wordBreak(s, wordDict));

        // Output: ["cat sand dog", "cats and dog"]

    }

}