150NeetCode 150

77.Palindrome Partitioning

MediumBacktracking · 7/9

Problem

Given a string s, split it into pieces so that every piece is a palindrome, and return all such partitions.

A partition is a list of substrings that, concatenated in order, give back s. A single character is always a palindrome, so at least one partition always exists.

Examples

Input:  s = "aab"
Output: [["a","a","b"], ["aa","b"]]
Explanation: "aab" and "ab" are not palindromes, so only these two splits work.
Input:  s = "a"
Output: [["a"]]
Input:  s = "efe"
Output: [["e","f","e"], ["efe"]]

Constraints

  • 1 <= s.length <= 16
  • s contains only lowercase English letters.

💡 Key Insight

Choose the first piece s[start..end]; if it is a palindrome, recursively partition the rest s[end+1..]. Every root-to-leaf path in this tree is one answer. The expensive part is the repeated palindrome check — precompute a table isPal[i][j] with DP (s[i] == s[j] and the inside isPal[i+1][j-1] is a palindrome) so each check is O(1).

Backtracking with Two-Pointer Check

TimeO(n · 2^n)SpaceO(n)

Intuition

At position start, try every possible end end. Check s[start..end] with two pointers moving inward. If it is a palindrome, add it to path and recurse from end + 1. Reaching start == n means the whole string has been cut into palindromes.

Algorithm

  1. dfs(start): if start == n, record a copy of path.
  2. For end from start to n - 1:
    • If isPalindrome(s, start, end): push s.substring(start, end + 1), dfs(end + 1), pop.

Visualization

flowchart TD
    R["start=0 'aab'"] -->|"'a' ✓"| A["start=1 'ab'"]
    R -->|"'aa' ✓"| B["start=2 'b'"]
    R -.->|"'aab' ✗"| X["pruned"]
    A -->|"'a' ✓"| A1["start=2 'b'"]
    A -.->|"'ab' ✗"| X2["pruned"]
    A1 -->|"'b' ✓"| A2["[a, a, b]"]
    B -->|"'b' ✓"| B1["[aa, b]"]

Code

java
import java.util.*;

class Solution {
    private final List<List<String>> result = new ArrayList<>();
    private final List<String> path = new ArrayList<>();

    public List<List<String>> partition(String s) {
        dfs(s, 0);
        return result;
    }

    private void dfs(String s, int start) {
        if (start == s.length()) {
            result.add(new ArrayList<>(path)); // whole string partitioned
            return;
        }
        for (int end = start; end < s.length(); end++) {
            if (isPalindrome(s, start, end)) {
                path.add(s.substring(start, end + 1));
                dfs(s, end + 1);
                path.remove(path.size() - 1);
            }
        }
    }

    private boolean isPalindrome(String s, int lo, int hi) {
        while (lo < hi) {
            if (s.charAt(lo++) != s.charAt(hi--)) return false;
        }
        return true;
    }
}

Complexity

  • Time: O(n · 2^n) — there are up to 2^(n-1) partitions (each gap is cut or not); each palindrome check and copy is O(n).
  • Space: O(n) — recursion depth and path (output excluded).

Complexity Comparison

Approach Time Space
Backtracking + two-pointer check O(n · 2^n) O(n)
Backtracking + DP table O(n · 2^n), cheaper checks O(n²)

Common Pitfalls

  • Off-by-one with substring(start, end + 1) — the end index of substring is exclusive.
  • Filling the DP table top-down by i — isPal[i+1][j-1] would not be computed yet. Iterate i from n - 1 to 0.
  • Forgetting the j - i < 2 base case, which reads isPal[i+1][i] (an empty/invalid range) for length-2 substrings.
  • Adding path instead of a copy to the result.