Last Updated: March 29, 2026
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.
Think of it like placing dividers in the string. For "aab", we could place dividers as "a|a|b" or "aa|b". Each placement that results in all palindromic pieces is a valid answer.
The key observation is that this is a decision problem at each position. Starting from the left, we try every possible palindromic prefix, and then recursively partition the remainder. If we can reach the end of the string with every piece being a palindrome, we have found a valid partitioning.
1 <= s.length <= 16 → With n at most 16, exponential approaches like backtracking are perfectly fine. In the worst case (all same characters), the number of valid partitions can be 2^(n-1), which for n=16 is 32,768. Very manageable.s contains only lowercase English letters → No special characters to worry about. Palindrome checks are straightforward character comparisons.The most natural way to approach this problem is to build partitions left to right. Starting from index 0, we try every possible first piece: "a", "aa", "aab". If a piece is a palindrome, we recurse on whatever is left. If we reach the end of the string, every piece we collected along the way is palindromic, so we have found a valid partition.
This is classic backtracking. At each position, we explore multiple choices (where to end the current piece), and we backtrack when we need to try a different choice.
For the palindrome check itself, the simplest approach is to just compare characters from both ends of the substring and work inward. This takes O(n) time per check, but with n at most 16, it is fast enough.
The repeated work here is in the palindrome checks. Every time we consider substring s[i..j], we re-check it character by character, even if we checked the same substring in a different branch. What if we precomputed all palindrome information upfront so every check became O(1)?
The backtracking logic from Approach 1 is already the right framework. We cannot avoid exploring all valid partitions. But we can eliminate redundant palindrome checks by precomputing a 2D table.
Here is the key insight for the precomputation: a substring s[i..j] is a palindrome if and only if s[i] == s[j] AND s[i+1..j-1] is also a palindrome (or the inner part has length 0 or 1, making it trivially a palindrome). This gives us a clean dynamic programming recurrence.
We fill a boolean table isPalin[i][j] bottom-up. Single characters are always palindromes. Two consecutive characters are palindromes if they match. For longer substrings, we check the two endpoints and look up the inner substring in our table.
Once this table is built, every palindrome check during backtracking becomes a simple O(1) table lookup instead of an O(n) character scan.
The palindrome table captures a simple recursive truth: a string is a palindrome if its first and last characters match and the string between them is also a palindrome. By filling this table bottom-up (starting from the shortest substrings), we ensure that when we compute isPalin[i][j], the answer for isPalin[i+1][j-1] is already available. During backtracking, instead of scanning characters each time, we simply look up isPalin[start][end]. The backtracking tree is exactly the same as Approach 1, but each node does less work.
isPalin of size n x n, where isPalin[i][j] is true if s[i..j] is a palindrome.isPalin[start][end].The palindrome table makes each check O(1), but different backtracking branches can still reach the same index and re-explore identical suffixes. What if we cached the results for each suffix so it is computed only once?
Notice something about the backtracking tree: when we reach index i, the set of all valid partitions of s[i..n-1] is the same regardless of how we got to index i. In Approach 2, we recompute this every time. But we could store it.
The idea is to define dp[i] as the list of all valid palindrome partitions of the suffix starting at index i. To compute dp[i], we try every index j from i to n-1 where s[i..j] is a palindrome. For each such j, we prepend s[i..j] to every partition in dp[j+1]. The base case is dp[n] = [[]] (an empty partition for the empty suffix).
This transforms the problem from backtracking into a bottom-up DP where we build from the end of the string backward.
Each suffix s[i..n-1] has a fixed set of valid palindrome partitions, regardless of what came before index i. By computing these bottom-up and storing them, we assemble the final answer by combining palindromic prefixes with already-computed suffix partitions. This is the classic DP pattern of breaking a problem into non-overlapping subproblems along a single dimension.