2Brute2Best
🔥0

Palindrome Partitioning

Medium·LeetCode #131Basic Anchor
LeetCode
30-Second Mental ModelBacktracking → String partitioning (cut and recurse on the rest)
Complexity:O(N · 2^(N−1))→O(N² + N · 2^N)
Key Insight

Fill isPal[i][j] once for every substring, using isPal[i][j] = (s[i] == s[j]) and isPal[i+1][j−1]. Then run the same backtracking as the better approach, but each piece check is a table lookup.

Core Validity Invariant

Every piece must be a palindrome, so the search cuts s[start..end] only when isPal[start][end] is true. The table answers that in O(1), so no letter is compared again during the search.

01

Problem

You are given a string s. Cut s into pieces so that every piece is a palindrome. A palindrome reads the same forwards and backwards, such as aa or aba. Find every way to cut s like this. Each way is a list of pieces that, joined in order, give back s.

Input

A string s of lowercase letters.

Output

A list of every valid way to cut s. Each way is a list of pieces.

Example 1
Input: s = "aab"
Output: [[a, a, b], [aa, b]]

a, a and b are palindromes, and so are aa and b. The pieces ab and aab are not palindromes, so they cannot be used.

Example 2
Input: s = "a"
Output: [[a]]

A single letter is a palindrome, so the only way is to keep the whole string as one piece.

Example 3
Input: s = "aba"
Output: [[a, b, a], [aba]]

aba is a palindrome, so the whole string can be one piece. It can also be cut into a, b and a.

Constraints
  • • 1 ≤ s.length ≤ 16
  • • `s` contains only lowercase English letters.
02

First, think yourself

Before opening any solution, try to answer these on paper. Use hints only when stuck — each hint is counted.

  1. 1.

    Each piece must be a palindrome. Do I need to check the whole string, or only the piece I am cutting off?

  2. 2.

    The answer is every valid way to cut. At the start of the string, what are my choices?

  3. 3.

    After I cut the first piece, what is left? Is that rest the same kind of problem?

  4. 4.

    For aab, how many places can I cut? How many cut sets is that in total?

  5. 5.

    If two different ways of cutting reach the same position, do I need to check the same piece twice?

03

Brute force approach

04

Better approach

05

Optimized approach

06

Why does this algorithm work?

07

Dry run

08

All approaches compared

ApproachIdeaTimeSpace
Brute ForceTry all 2^(n−1) cut sets and keep one only if every piece is a palindromeO(N · 2^(N−1))O(N)
BetterRecurse only on palindrome pieces, so a bad piece is never built onO(N² · 2^N)O(N)
OptimalFill a palindrome table in O(N²); the search then checks each piece in O(1)O(N² + N · 2^N)O(N²)
09

Why does this belong to this pattern?

Clues in the problem statement
  • “every piece is a palindrome”The rule is checked on each piece, so a bad piece can stop its branch right away.
  • “find every way to cut”The answer is a list of all valid cut sets, so every valid route must be kept, not just one count.
  • “string”The choices are cut points. After a cut, the rest of the string is the same problem, only shorter.
Cut at every valid position+recurse on the rest+check each piece as a palindrome+undo the piece after the call→Backtracking → String partitioning (cut and recurse on the rest)

This is the first string partitioning question. The decision is where each piece ends, and the rule is local: each piece is checked on its own, not the whole string at once. That is different from Valid Palindrome, which checks the whole string once. The next two questions keep the same cut-and-recurse shape, but the rule changes: Restore IP Addresses checks the value and format of each part, and Word Break II checks each piece against a dictionary.

10

Don't make this mistake

Pattern confusion: checking the whole remainder instead of the piece
✗ if (!isPal(s, start, (int)s.size() - 1)) continue; // checks the rest of the string
✓ if (!isPal(s, start, end)) continue; // checks only the piece being cut

This treats the problem like Valid Palindrome: one check on the whole remainder. On aab, the remainder from start 0 is aab, which is not a palindrome. So every end is skipped, and the answer is [] instead of [[a, a, b], [aa, b]].

Forgetting to pop the piece after the call
✗ path.push_back(s.substr(start, end - start + 1)); dfs(end + 1, s, path, out); // no pop_back
✓ path.push_back(s.substr(start, end - start + 1)); dfs(end + 1, s, path, out); path.pop_back();

The piece a from the first cut stays in path when the search moves on. On aab, the pieces a, a, b are left in place, so the second answer becomes [a, a, b, aa, b]. Its pieces spell aabaab, not aab.

Filling the isPal table in the wrong order
✗ for (int i = 0; i < n; i++) for (int j = i; j < n; j++) isPal[i][j] = s[i] == s[j] && (j - i < 2 || isPal[i + 1][j - 1]);
✓ for (int i = n - 1; i >= 0; i--) for (int j = i; j < n; j++) isPal[i][j] = s[i] == s[j] && (j - i < 2 || isPal[i + 1][j - 1]);

isPal[i+1][j−1] must be filled before isPal[i][j] reads it. With i going up, row i + 1 is still all false. On aaa, isPal[0][2] reads isPal[1][1] too early, gets false, and the answer misses [aaa].

11

Want to implement this?

Write it yourself on LeetCode, then come back and compare your code with the solutions above.

Open coding workspace
How did it go?
Beyond The LeetCode Problem

How this concept connects to System Design & C++ Systems

Bridge DSA with production engineering

Top engineering interviews test whether you understand how algorithms like Backtracking translate into production architectures and low-level memory mechanics.