Palindrome Partitioning
Imagine you want to split a secret message into palindromic chunks so that each chunk reads the same forwards and backwards, making it easier to encode or analyze.
Given a string s, partition s such that every substring of the partition is a palindrome. Return all possible palindrome partitioning of s.
1 ≤ s.length ≤ 16s consists of lowercase English letters only"aab"aabaabThe string 'aab' can be partitioned into palindromes as ['a','a','b'] and ['aa','b'].
- Empty string → [] (no partitions)
- Single character string → [[char]] (only one partition)
- String with all identical characters → multiple partitions with varying palindrome lengths
- String with no palindromic substrings longer than 1 → partitions of single characters only
Leads to incorrect partitions or duplicates in results
✅ Always pop the last added substring after recursive call returns
Causes timeouts or slow performance
✅ Memoize palindrome checks or precompute palindrome table
May cause errors or miss valid partitions
✅ Add base case for empty string and handle single characters as palindromes
Results contain references to the same list, causing wrong outputs
✅ Add a copy of the current path (e.g., path[:], new ArrayList<>(path)) to results
Incorrect substrings lead to wrong partitions or runtime errors
✅ Carefully use substring methods with correct start and end indices
Intuition
Try every possible substring starting from the current index. If the substring is a palindrome, recursively partition the remaining string. Collect all valid partitions.
Algorithm
- Start from index 0 and try all substrings s[start:end].
- Check if the substring is a palindrome.
- If yes, add it to the current partition and recurse from end index.
- When start reaches the end of the string, add the current partition to results.
def partition(s):
def is_palindrome(sub):
return sub == sub[::-1]
def backtrack(start, path):
if start == len(s):
result.append(path[:])
return
for end in range(start + 1, len(s) + 1):
substr = s[start:end]
if is_palindrome(substr):
path.append(substr)
backtrack(end, path)
path.pop()
result = []
backtrack(0, [])
return result
# Example usage
if __name__ == '__main__':
print(partition("aab"))def is_palindrome(sub):Helper function to check palindrome property of substringreturn sub == sub[::-1]Simple palindrome check by reversing stringdef backtrack(start, path):Recursive function exploring partitions from index startif start == len(s):Base case: reached end of string, add current partition to resultsfor end in range(start + 1, len(s) + 1):Try all possible substring ends from startsubstr = s[start:end]Extract substring to checkif is_palindrome(substr):Only proceed if substring is palindrome to prune searchpath.append(substr)Choose this substring and add to current pathbacktrack(end, path)Recurse to partition remaining stringpath.pop()Backtrack to explore other partitionsresult = []Stores all valid palindrome partitionsimport java.util.*;
public class PalindromePartitioning {
public List<List<String>> partition(String s) {
List<List<String>> result = new ArrayList<>();
backtrack(s, 0, new ArrayList<>(), result);
return result;
}
private void backtrack(String s, int start, List<String> path, List<List<String>> result) {
if (start == s.length()) {
result.add(new ArrayList<>(path));
return;
}
for (int end = start + 1; end <= s.length(); end++) {
String substr = s.substring(start, end);
if (isPalindrome(substr)) {
path.add(substr);
backtrack(s, end, path, result);
path.remove(path.size() - 1);
}
}
}
private boolean isPalindrome(String str) {
int left = 0, right = str.length() - 1;
while (left < right) {
if (str.charAt(left++) != str.charAt(right--)) return false;
}
return true;
}
public static void main(String[] args) {
PalindromePartitioning sol = new PalindromePartitioning();
System.out.println(sol.partition("aab"));
}
}public List<List<String>> partition(String s)Main function to start partitioningbacktrack(s, 0, new ArrayList<>(), result);Start recursion from index 0 with empty pathif (start == s.length())Base case: full partition found, add copy to resultfor (int end = start + 1; end <= s.length(); end++)Try all substring ends from startString substr = s.substring(start, end);Extract substring to checkif (isPalindrome(substr))Prune search by checking palindrome validitypath.add(substr);Choose substring and add to current partition pathbacktrack(s, end, path, result);Recurse for remaining stringpath.remove(path.size() - 1);Backtrack to explore other partitionsprivate boolean isPalindrome(String str)Helper to check palindrome by two pointerswhile (left < right)Compare characters from both ends moving inward#include <iostream>
#include <vector>
#include <string>
using namespace std;
class Solution {
public:
vector<vector<string>> partition(string s) {
vector<vector<string>> result;
vector<string> path;
backtrack(s, 0, path, result);
return result;
}
private:
void backtrack(const string& s, int start, vector<string>& path, vector<vector<string>>& result) {
if (start == s.size()) {
result.push_back(path);
return;
}
for (int end = start; end < s.size(); ++end) {
if (isPalindrome(s, start, end)) {
path.push_back(s.substr(start, end - start + 1));
backtrack(s, end + 1, path, result);
path.pop_back();
}
}
}
bool isPalindrome(const string& s, int left, int right) {
while (left < right) {
if (s[left++] != s[right--]) return false;
}
return true;
}
};
int main() {
Solution sol;
auto res = sol.partition("aab");
for (auto& partition : res) {
cout << "[";
for (auto& str : partition) cout << str << ",";
cout << "]\n";
}
return 0;
}vector<vector<string>> partition(string s)Main function to initiate backtrackingbacktrack(s, 0, path, result);Start recursion from index 0 with empty pathif (start == s.size())Base case: full partition found, add to resultfor (int end = start; end < s.size(); ++end)Try all substring ends from startif (isPalindrome(s, start, end))Check palindrome to prune invalid partitionspath.push_back(s.substr(start, end - start + 1));Add current palindrome substring to pathbacktrack(s, end + 1, path, result);Recurse for remaining substringpath.pop_back();Backtrack to try other partitionsbool isPalindrome(const string& s, int left, int right)Helper to check palindrome using two pointerswhile (left < right)Compare characters inward from both endsfunction partition(s) {
const result = [];
function isPalindrome(sub) {
let left = 0, right = sub.length - 1;
while (left < right) {
if (sub[left++] !== sub[right--]) return false;
}
return true;
}
function backtrack(start, path) {
if (start === s.length) {
result.push([...path]);
return;
}
for (let end = start + 1; end <= s.length; end++) {
const substr = s.slice(start, end);
if (isPalindrome(substr)) {
path.push(substr);
backtrack(end, path);
path.pop();
}
}
}
backtrack(0, []);
return result;
}
// Example usage
console.log(partition("aab"));function isPalindrome(sub)Helper function to check if substring is palindromelet left = 0, right = sub.length - 1;Initialize pointers for palindrome checkwhile (left < right)Two-pointer palindrome check moving inwardif (sub[left++] !== sub[right--]) return false;Return false immediately if mismatch foundfunction backtrack(start, path)Recursive function to explore partitions from startif (start === s.length)Base case: reached end, add current partition copy to resultfor (let end = start + 1; end <= s.length; end++)Try all substring ends from startconst substr = s.slice(start, end);Extract substring to checkif (isPalindrome(substr))Prune search by palindrome validationpath.push(substr)Add substring to current partition pathbacktrack(end, path)Recurse for remaining stringpath.pop()Backtrack to try other partitionsresult.push([...path])Add a copy of current partition to resultsO(N * 2^N)O(N)There are up to 2^(N-1) ways to partition a string of length N. Each partition requires palindrome checks which take O(N) time, leading to O(N * 2^N) time complexity.
This approach is simple but inefficient; it helps understand the problem but is impractical for large strings due to exponential time.
Intuition
Store results of palindrome checks in a 2D table so that each substring is checked only once. Use this table during backtracking to prune invalid partitions quickly.
Algorithm
- Precompute a 2D boolean table is_palindrome[i][j] indicating if s[i:j] is palindrome.
- Use backtracking to generate partitions, but check palindrome validity from the table in O(1).
- Add valid palindrome substrings to current path and recurse.
- Add completed partitions to results when end of string is reached.
def partition(s):
n = len(s)
is_palindrome = [[False]*n for _ in range(n)]
for i in range(n-1, -1, -1):
for j in range(i, n):
if s[i] == s[j] and (j - i < 3 or is_palindrome[i+1][j-1]):
is_palindrome[i][j] = True
def backtrack(start, path):
if start == n:
result.append(path[:])
return
for end in range(start, n):
if is_palindrome[start][end]:
path.append(s[start:end+1])
backtrack(end+1, path)
path.pop()
result = []
backtrack(0, [])
return result
# Example usage
if __name__ == '__main__':
print(partition("aab"))n = len(s)Store length of string for convenienceis_palindrome = [[False]*n for _ in range(n)]Initialize 2D table to store palindrome infofor i in range(n-1, -1, -1)Fill table bottom-up to use previously computed resultsfor j in range(i, n)Check all substrings starting at iif s[i] == s[j] and (j - i < 3 or is_palindrome[i+1][j-1])Check palindrome condition using smaller substring infois_palindrome[i][j] = TrueMark substring as palindromedef backtrack(start, path)Backtracking function uses precomputed palindrome tableif start == nBase case: reached end, add current partition to resultsfor end in range(start, n)Try all substring ends from startif is_palindrome[start][end]Check palindrome in O(1) from table instead of recomputingpath.append(s[start:end+1])Add valid palindrome substring to current pathbacktrack(end+1, path)Recurse for remaining substringpath.pop()Backtrack to explore other partitionsresult = []Stores all valid palindrome partitionsimport java.util.*;
public class PalindromePartitioning {
public List<List<String>> partition(String s) {
int n = s.length();
boolean[][] isPalindrome = new boolean[n][n];
for (int i = n - 1; i >= 0; i--) {
for (int j = i; j < n; j++) {
if (s.charAt(i) == s.charAt(j) && (j - i < 3 || isPalindrome[i + 1][j - 1])) {
isPalindrome[i][j] = true;
}
}
}
List<List<String>> result = new ArrayList<>();
backtrack(s, 0, new ArrayList<>(), result, isPalindrome);
return result;
}
private void backtrack(String s, int start, List<String> path, List<List<String>> result, boolean[][] isPalindrome) {
if (start == s.length()) {
result.add(new ArrayList<>(path));
return;
}
for (int end = start; end < s.length(); end++) {
if (isPalindrome[start][end]) {
path.add(s.substring(start, end + 1));
backtrack(s, end + 1, path, result, isPalindrome);
path.remove(path.size() - 1);
}
}
}
public static void main(String[] args) {
PalindromePartitioning sol = new PalindromePartitioning();
System.out.println(sol.partition("aab"));
}
}int n = s.length()Store length of string for convenienceboolean[][] isPalindrome = new boolean[n][n];2D array to store palindrome info for substringsfor (int i = n - 1; i >= 0; i--)Fill palindrome table bottom-up for reusefor (int j = i; j < n; j++)Check all substrings starting at iif (s.charAt(i) == s.charAt(j) && (j - i < 3 || isPalindrome[i + 1][j - 1]))Palindrome condition using smaller substring infoisPalindrome[i][j] = trueMark substring as palindromebacktrack(s, 0, new ArrayList<>(), result, isPalindrome);Start backtracking with precomputed palindrome tableif (start == s.length())Base case: full partition found, add copy to resultfor (int end = start; end < s.length(); end++)Try all substring ends from startif (isPalindrome[start][end])Check palindrome in O(1) during recursionpath.add(s.substring(start, end + 1));Add palindrome substring to current pathbacktrack(s, end + 1, path, result, isPalindrome);Recurse for remaining substringpath.remove(path.size() - 1);Backtrack to explore other partitions#include <iostream>
#include <vector>
#include <string>
using namespace std;
class Solution {
public:
vector<vector<string>> partition(string s) {
int n = s.size();
vector<vector<bool>> isPalindrome(n, vector<bool>(n, false));
for (int i = n - 1; i >= 0; --i) {
for (int j = i; j < n; ++j) {
if (s[i] == s[j] && (j - i < 3 || isPalindrome[i + 1][j - 1])) {
isPalindrome[i][j] = true;
}
}
}
vector<vector<string>> result;
vector<string> path;
backtrack(s, 0, path, result, isPalindrome);
return result;
}
private:
void backtrack(const string& s, int start, vector<string>& path, vector<vector<string>>& result, const vector<vector<bool>>& isPalindrome) {
if (start == s.size()) {
result.push_back(path);
return;
}
for (int end = start; end < s.size(); ++end) {
if (isPalindrome[start][end]) {
path.push_back(s.substr(start, end - start + 1));
backtrack(s, end + 1, path, result, isPalindrome);
path.pop_back();
}
}
}
};
int main() {
Solution sol;
auto res = sol.partition("aab");
for (auto& partition : res) {
cout << "[";
for (auto& str : partition) cout << str << ",";
cout << "]\n";
}
return 0;
}int n = s.size()Store length of string for conveniencevector<vector<bool>> isPalindrome(n, vector<bool>(n, false));2D vector to store palindrome infofor (int i = n - 1; i >= 0; --i)Fill palindrome table bottom-up for reusefor (int j = i; j < n; ++j)Check all substrings starting at iif (s[i] == s[j] && (j - i < 3 || isPalindrome[i + 1][j - 1]))Palindrome condition using smaller substring infoisPalindrome[i][j] = trueMark substring as palindromebacktrack(s, 0, path, result, isPalindrome);Start backtracking with precomputed palindrome tableif (start == s.size())Base case: full partition found, add to resultfor (int end = start; end < s.size(); ++end)Try all substring ends from startif (isPalindrome[start][end])Check palindrome in O(1) during recursionpath.push_back(s.substr(start, end - start + 1));Add palindrome substring to current pathbacktrack(s, end + 1, path, result, isPalindrome);Recurse for remaining substringpath.pop_back();Backtrack to explore other partitionsfunction partition(s) {
const n = s.length;
const isPalindrome = Array.from({ length: n }, () => Array(n).fill(false));
for (let i = n - 1; i >= 0; i--) {
for (let j = i; j < n; j++) {
if (s[i] === s[j] && (j - i < 3 || isPalindrome[i + 1][j - 1])) {
isPalindrome[i][j] = true;
}
}
}
const result = [];
function backtrack(start, path) {
if (start === n) {
result.push([...path]);
return;
}
for (let end = start; end < n; end++) {
if (isPalindrome[start][end]) {
path.push(s.slice(start, end + 1));
backtrack(end + 1, path);
path.pop();
}
}
}
backtrack(0, []);
return result;
}
// Example usage
console.log(partition("aab"));const n = s.lengthStore length of string for convenienceconst isPalindrome = Array.from({ length: n }, () => Array(n).fill(false));Initialize 2D array for palindrome infofor (let i = n - 1; i >= 0; i--)Fill palindrome table bottom-up for reusefor (let j = i; j < n; j++)Check all substrings starting at iif (s[i] === s[j] && (j - i < 3 || isPalindrome[i + 1][j - 1]))Palindrome condition using smaller substring infoisPalindrome[i][j] = trueMark substring as palindromefunction backtrack(start, path)Backtracking function uses precomputed palindrome tableif (start === n)Base case: reached end, add current partition to resultsfor (let end = start; end < n; end++)Try all substring ends from startif (isPalindrome[start][end])Check palindrome in O(1) during recursionpath.push(s.slice(start, end + 1))Add palindrome substring to current pathbacktrack(end + 1, path)Recurse for remaining substringpath.pop()Backtrack to explore other partitionsO(N * 2^N)O(N^2)Precomputing palindrome table takes O(N^2). Backtracking still explores up to 2^N partitions but palindrome checks are O(1), improving efficiency.
This approach balances clarity and efficiency, making it a strong candidate for coding in interviews.
Intuition
At each recursion step, expand around the current substring to check palindrome validity without a large DP table, then recurse if valid.
Algorithm
- At each recursion, try all substrings starting at current index.
- Check palindrome by expanding around substring boundaries.
- If palindrome, add substring to path and recurse.
- Add completed partitions to results when end of string is reached.
def partition(s):
def is_palindrome(left, right):
while left < right:
if s[left] != s[right]:
return False
left += 1
right -= 1
return True
def backtrack(start, path):
if start == len(s):
result.append(path[:])
return
for end in range(start, len(s)):
if is_palindrome(start, end):
path.append(s[start:end+1])
backtrack(end+1, path)
path.pop()
result = []
backtrack(0, [])
return result
# Example usage
if __name__ == '__main__':
print(partition("aab"))def is_palindrome(left, right):Check palindrome by expanding around substring boundarieswhile left < right:Compare characters inward from both endsif s[left] != s[right]:Return False immediately if mismatch foundleft += 1Move left pointer inwardright -= 1Move right pointer inwardreturn TrueSubstring is palindrome if no mismatches founddef backtrack(start, path):Recursive function to explore partitionsif start == len(s):Base case: reached end, add current partition copy to resultfor end in range(start, len(s))Try all substring ends from startif is_palindrome(start, end)Check palindrome dynamically before recursionpath.append(s[start:end+1])Add palindrome substring to current pathbacktrack(end+1, path)Recurse for remaining substringpath.pop()Backtrack to explore other partitionsresult = []Stores all valid palindrome partitionsimport java.util.*;
public class PalindromePartitioning {
public List<List<String>> partition(String s) {
List<List<String>> result = new ArrayList<>();
backtrack(s, 0, new ArrayList<>(), result);
return result;
}
private void backtrack(String s, int start, List<String> path, List<List<String>> result) {
if (start == s.length()) {
result.add(new ArrayList<>(path));
return;
}
for (int end = start; end < s.length(); end++) {
if (isPalindrome(s, start, end)) {
path.add(s.substring(start, end + 1));
backtrack(s, end + 1, path, result);
path.remove(path.size() - 1);
}
}
}
private boolean isPalindrome(String s, int left, int right) {
while (left < right) {
if (s.charAt(left++) != s.charAt(right--)) return false;
}
return true;
}
public static void main(String[] args) {
PalindromePartitioning sol = new PalindromePartitioning();
System.out.println(sol.partition("aab"));
}
}private void backtrack(String s, int start, List<String> path, List<List<String>> result)Recursive function exploring partitionsif (start == s.length())Base case: full partition found, add copy to resultfor (int end = start; end < s.length(); end++)Try all substring ends from startif (isPalindrome(s, start, end))Check palindrome dynamically before recursionpath.add(s.substring(start, end + 1));Add palindrome substring to current pathbacktrack(s, end + 1, path, result);Recurse for remaining substringpath.remove(path.size() - 1);Backtrack to explore other partitionsprivate boolean isPalindrome(String s, int left, int right)Check palindrome by expanding around substring boundarieswhile (left < right)Compare characters inward from both endsif (s.charAt(left++) != s.charAt(right--)) return false;Return false immediately if mismatch#include <iostream>
#include <vector>
#include <string>
using namespace std;
class Solution {
public:
vector<vector<string>> partition(string s) {
vector<vector<string>> result;
vector<string> path;
backtrack(s, 0, path, result);
return result;
}
private:
void backtrack(const string& s, int start, vector<string>& path, vector<vector<string>>& result) {
if (start == s.size()) {
result.push_back(path);
return;
}
for (int end = start; end < s.size(); ++end) {
if (isPalindrome(s, start, end)) {
path.push_back(s.substr(start, end - start + 1));
backtrack(s, end + 1, path, result);
path.pop_back();
}
}
}
bool isPalindrome(const string& s, int left, int right) {
while (left < right) {
if (s[left++] != s[right--]) return false;
}
return true;
}
};
int main() {
Solution sol;
auto res = sol.partition("aab");
for (auto& partition : res) {
cout << "[";
for (auto& str : partition) cout << str << ",";
cout << "]\n";
}
return 0;
}void backtrack(const string& s, int start, vector<string>& path, vector<vector<string>>& result)Recursive function exploring partitionsif (start == s.size())Base case: full partition found, add to resultfor (int end = start; end < s.size(); ++end)Try all substring ends from startif (isPalindrome(s, start, end))Check palindrome dynamically before recursionpath.push_back(s.substr(start, end - start + 1));Add palindrome substring to current pathbacktrack(s, end + 1, path, result);Recurse for remaining substringpath.pop_back();Backtrack to explore other partitionsbool isPalindrome(const string& s, int left, int right)Check palindrome by expanding around substring boundarieswhile (left < right)Compare characters inward from both endsif (s[left++] != s[right--]) return false;Return false immediately if mismatchfunction partition(s) {
const result = [];
function isPalindrome(left, right) {
while (left < right) {
if (s[left] !== s[right]) return false;
left++;
right--;
}
return true;
}
function backtrack(start, path) {
if (start === s.length) {
result.push([...path]);
return;
}
for (let end = start; end < s.length; end++) {
if (isPalindrome(start, end)) {
path.push(s.slice(start, end + 1));
backtrack(end + 1, path);
path.pop();
}
}
}
backtrack(0, []);
return result;
}
// Example usage
console.log(partition("aab"));function isPalindrome(left, right)Check palindrome by expanding around substring boundarieswhile (left < right)Compare characters inward from both endsif (s[left] !== s[right]) return false;Return false immediately if mismatchleft++Move left pointer inwardright--Move right pointer inwardreturn trueSubstring is palindrome if no mismatches foundfunction backtrack(start, path)Recursive function exploring partitionsif (start === s.length)Base case: full partition found, add copy to resultfor (let end = start; end < s.length; end++)Try all substring ends from startif (isPalindrome(start, end))Check palindrome dynamically before recursionpath.push(s.slice(start, end + 1))Add palindrome substring to current pathbacktrack(end + 1, path)Recurse for remaining substringpath.pop()Backtrack to explore other partitionsO(N * 2^N)O(N)Palindrome checks take O(N) each, and there are up to 2^N partitions, so total time is O(N * 2^N). Space is O(N) for recursion stack and path.
This approach is useful when memory is limited but still efficient enough for typical interview constraints.
| Approach | Time | Space | Stack Risk | Reconstruct | Use In Interview |
|---|---|---|---|---|---|
| 1. Brute Force | O(N * 2^N) | O(N) | Yes (deep recursion) | Yes | Mention only - never code |
| 2. Backtracking with Palindrome Memoization | O(N * 2^N) | O(N^2) | Yes (deep recursion) | Yes | Code this approach in interviews |
| 3. Backtracking with Dynamic Palindrome Check | O(N * 2^N) | O(N) | Yes (deep recursion) | Yes | Mention as memory-optimized alternative |
How to Present
Step 1: Clarify the problem and constraints.Step 2: Describe the brute force recursive approach with palindrome checks.Step 3: Explain how to optimize palindrome checks with memoization.Step 4: Optionally mention dynamic palindrome checking to save space.Step 5: Code the chosen approach and test with examples.
Time Allocation
What the Interviewer Tests
The interviewer tests your understanding of backtracking, pruning, palindrome checking, and optimization techniques.
Common Follow-ups
- How to find the minimum cuts needed for palindrome partitioning? → Use DP to find minimum cuts.
- Can you optimize palindrome checking further? → Use Manacher's algorithm or expand around center.
When to Use
1) Asked to partition string into substrings; 2) Each substring must satisfy a property (palindrome); 3) Need all possible partitions; 4) Problem hints at recursion or backtracking.
