AlgoMaster Logo

Palindrome Partitioning

mediumFrequency7 min readUpdated June 23, 2026

Understanding the Problem

We need to find every way to split a string into parts where each part reads the same forwards and backwards. This is not about finding the fewest cuts or the longest palindrome. We need all valid partitionings.

Splitting a string means placing dividers between characters. For "aab", the dividers "a|a|b" and "aa|b" both produce all-palindromic pieces, so both are valid answers.

This is a sequence of decisions, one per position. Starting from the left, we pick a palindromic prefix, then recursively partition the remainder. When we reach the end of the string with every piece being a palindrome, we have a valid partitioning.

Key Constraints:

  • 1 <= s.length <= 16 → With n capped at 16, an exponential backtracking search is fine. In the worst case (all same characters), the number of valid partitions is 2^(n-1), which for n=16 is 32,768.
  • s contains only lowercase English letters → Palindrome checks are plain character comparisons with no escaping or case folding.

Approach 1: Backtracking with Inline Palindrome Checks

Intuition

Build partitions left to right. Starting from index 0, try every possible first piece: "a", "aa", "aab". If a piece is a palindrome, recurse on the rest of the string. On reaching the end, every piece collected along the way is palindromic, so the path is a valid partition.

This is backtracking. At each position we explore multiple choices (where to end the current piece) and undo a choice before trying the next one.

To check whether a piece is a palindrome, compare characters from both ends and move inward. This takes O(n) time per check, which is fine when n is at most 16.

Algorithm

  1. Start a recursive function with the current index in the string and the current list of palindromic pieces collected so far.
  2. If the current index equals the length of the string, we have partitioned the entire string. Add a copy of the current list to the results.
  3. Otherwise, try every possible end index from the current index to the end of the string.
  4. For each end index, check if the substring from the current index to the end index is a palindrome.
  5. If it is, add it to the current list, recurse with the next index being end + 1, then remove the substring (backtrack).

Example Walkthrough

1Start: try s[0..0] = "a", palindrome check passes
0
a
try "a"
1
a
2
b
1/9

Code

The repeated work here is in the palindrome checks. Every time we consider substring s[i..j], we re-scan it character by character, even if we already checked the same substring in a different branch. Precomputing all palindrome information once turns each check into an O(1) lookup.

Approach 2: Backtracking with DP-Precomputed Palindrome Table

Intuition

The backtracking from Approach 1 already explores exactly the partitions we need, so the search itself stays. What changes is the palindrome check: a 2D table lets us answer "is s[i..j] a palindrome?" in O(1).

The recurrence behind the table: a substring s[i..j] is a palindrome if and only if s[i] == s[j] and the inner substring s[i+1..j-1] is also a palindrome. When the inner part has length 0 or 1, it is a palindrome by definition, so any substring of length 1 or 2 with matching endpoints qualifies directly.

We fill the boolean table isPalin[i][j] in order of increasing substring length. By the time we evaluate a substring of length L, every substring of length L-2 has already been filled, so the inner lookup isPalin[i+1][j-1] is ready. After the table is built, the backtracking tree is identical to Approach 1, but each node does an O(1) lookup instead of an O(n) scan.

Algorithm

  1. Build a 2D boolean table isPalin of size n x n, where isPalin[i][j] is true if s[i..j] is a palindrome.
  2. Fill the table: iterate over all possible substring lengths from 1 to n. For each length, check all starting positions.
  3. Run the same backtracking as Approach 1, but replace the isPalindrome function call with a table lookup isPalin[start][end].

Example Walkthrough

1Start backtracking from index 0. Try s[0..0] = "a"
0
a
try "a"
1
a
2
b
1/10

Code

The palindrome table makes each check O(1), but different backtracking branches still re-explore the same suffix. Whenever the search reaches index i, it recomputes the partitions of s[i..n-1] from scratch, even though that set never depends on how we arrived at i. The next approach computes each suffix once and stores it.

Approach 3: Bottom-Up DP Over Suffixes

Intuition

The set of valid partitions of a suffix s[i..n-1] depends only on i, not on the choices made before index i. Approach 2 recomputes this set in every branch that reaches i. We can compute it once instead.

Define dp[i] as the list of all valid palindrome partitions of the suffix starting at index i. To build dp[i], try every index j from i to n-1 where s[i..j] is a palindrome. For each such j, prepend the piece s[i..j] to every partition already stored in dp[j+1]. The base case is dp[n] = [[]], the single empty partition of the empty suffix.

Because dp[i] only ever reads dp[j+1] for j >= i, filling the array from right to left guarantees every dependency is ready before it is needed. The result for the whole string is dp[0].

Algorithm

  1. Precompute the isPalin table as in Approach 2.
  2. Create an array dp where dp[i] holds all valid partitions of the suffix s[i..n-1].
  3. Set the base case dp[n] = [[]] (the single empty partition of the empty suffix).
  4. Build from right to left: for each start index i, try every palindromic prefix s[i..j] and prepend it to each partition in dp[j+1].
  5. Return dp[0].

Example Walkthrough

1Build dp from right to left. dp[3] = [[]] (base case).
0
a
1
a
2
b
1/8

Code