Palindrome Partitioning
Medium · Backtracking · Explanation and code example
Recursion
How it works
This approach combines Dynamic Programming and pure recursion (return-based).
- We first precompute all palindromic substrings using DP.
- Then we use recursion where each recursive call:
- returns all valid palindrome partitions starting from a given index.
- Instead of maintaining a global result or path, each recursive call builds and returns its own list of partitions, which makes the logic clean and declarative.
Think of it as:
“All partitions starting at index
i=
choose a palindromes[i..j]+ all partitions starting atj + 1”
Steps
Step 1: Precompute Palindromes (DP)
- Create a 2D table
dp[i][j]. dp[i][j] = Trueif substrings[i..j]is a palindrome.- Fill the table by increasing substring length:
- Characters at ends must match.
- Inner substring must already be a palindrome (or be empty).
Step 2: Recursive Construction
- Define
dfs(i):- Returns all palindrome partitions starting from index
i.
- Returns all palindrome partitions starting from index
- Base case:
- If
i == len(s), return[[]](one empty partition).
- If
- Recursive case:
- For every
jfromito end:- If
dp[i][j]istrue:- Recursively get partitions from
dfs(j + 1). - Prepend
s[i..j]to each returned partition.
- Recursively get partitions from
- If
- For every
- Return the collected partitions.
Complexity
- Time complexity:
O(n * 2 ^ n) - Space complexity:
O(n ^ 2)extra space.O(n * 2 ^ n)space for the output list.
Things to watch for
Forgetting to Copy the Partition Before Adding to Results
When adding the current partition to the results list, you must add a copy of the list, not a reference to it. Since backtracking modifies the same partition list, adding the reference directly means all entries in the result will point to the same (eventually empty) list.
Incorrect Base Case in Backtracking
The base case should trigger when the starting index reaches the end of the string, indicating a complete valid partition. A common mistake is to check i > len(s) instead of i >= len(s) or i == len(s), causing missed partitions or index errors.
Redundant Palindrome Checks Without Memoization
Repeatedly checking whether the same substring is a palindrome across different recursion branches wastes time. Without precomputing palindrome information using DP, the same substring may be checked O(2^n) times, significantly slowing down the solution for longer strings.
Python code example
from typing import List
class Solution:
def partition(self, s: str) -> List[List[str]]:
n = len(s)
dp = [[False] * n for _ in range(n)]
for l in range(1, n + 1):
for i in range(n - l + 1):
dp[i][i + l - 1] = (s[i] == s[i + l - 1] and
(i + 1 > (i + l - 2) or
dp[i + 1][i + l - 2]))
def dfs(i):
if i >= n:
return [[]]
ret = []
for j in range(i, n):
if dp[i][j]:
nxt = dfs(j + 1)
for part in nxt:
cur = [s[i : j + 1]] + part
ret.append(cur)
return ret
return dfs(0)
C++ code example
class Solution {
public:
vector<vector<string>> partition(string s) {
int n = s.size();
vector<vector<bool>> dp(n, vector<bool>(n, false));
for (int l = 1; l <= n; l++) {
for (int i = 0; i <= n - l; i++) {
dp[i][i + l - 1] = (s[i] == s[i + l - 1] &&
(i + 1 > (i + l - 2) ||
dp[i + 1][i + l - 2]));
}
}
return dfs(s, dp, 0);
}
vector<vector<string>> dfs(string& s, vector<vector<bool>>& dp, int i) {
if (i >= s.size()) {
return {{}};
}
vector<vector<string>> ret;
for (int j = i; j < s.size(); j++) {
if (dp[i][j]) {
auto nxt = dfs(s, dp, j + 1);
for (auto& part : nxt) {
vector<string> cur;
cur.push_back(s.substr(i, j - i + 1));
cur.insert(cur.end(), part.begin(), part.end());
ret.push_back(cur);
}
}
}
return ret;
}
};
Source and license
Adapted from NeetCode’s source · MIT license. Node types are supplied by LeetCode where needed.