🧠
Backtracking with Palindrome Memoization
💡 This approach improves brute force by caching palindrome checks to avoid repeated work, making the solution faster and more practical. Think of it as having a cheat sheet that tells you instantly if any substring is a palindrome, so you don't waste time checking it multiple times.
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.
💡 Precomputing palindrome info upfront reduces repeated substring checks during recursion, improving efficiency.
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"))
Line Notes
n = len(s)Store length of string for convenience
is_palindrome = [[False]*n for _ in range(n)]Initialize 2D table to store palindrome info
for i in range(n-1, -1, -1)Fill table bottom-up to use previously computed results
for j in range(i, n)Check all substrings starting at i
if s[i] == s[j] and (j - i < 3 or is_palindrome[i+1][j-1])Check palindrome condition using smaller substring info
is_palindrome[i][j] = TrueMark substring as palindrome
def backtrack(start, path)Backtracking function uses precomputed palindrome table
if start == nBase case: reached end, add current partition to results
for end in range(start, n)Try all substring ends from start
if is_palindrome[start][end]Check palindrome in O(1) from table instead of recomputing
path.append(s[start:end+1])Add valid palindrome substring to current path
backtrack(end+1, path)Recurse for remaining substring
path.pop()Backtrack to explore other partitions
result = []Stores all valid palindrome partitions
import 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"));
}
}
Line Notes
int n = s.length()Store length of string for convenience
boolean[][] isPalindrome = new boolean[n][n];2D array to store palindrome info for substrings
for (int i = n - 1; i >= 0; i--)Fill palindrome table bottom-up for reuse
for (int j = i; j < n; j++)Check all substrings starting at i
if (s.charAt(i) == s.charAt(j) && (j - i < 3 || isPalindrome[i + 1][j - 1]))Palindrome condition using smaller substring info
isPalindrome[i][j] = trueMark substring as palindrome
backtrack(s, 0, new ArrayList<>(), result, isPalindrome);Start backtracking with precomputed palindrome table
if (start == s.length())Base case: full partition found, add copy to result
for (int end = start; end < s.length(); end++)Try all substring ends from start
if (isPalindrome[start][end])Check palindrome in O(1) during recursion
path.add(s.substring(start, end + 1));Add palindrome substring to current path
backtrack(s, end + 1, path, result, isPalindrome);Recurse for remaining substring
path.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;
}
Line Notes
int n = s.size()Store length of string for convenience
vector<vector<bool>> isPalindrome(n, vector<bool>(n, false));2D vector to store palindrome info
for (int i = n - 1; i >= 0; --i)Fill palindrome table bottom-up for reuse
for (int j = i; j < n; ++j)Check all substrings starting at i
if (s[i] == s[j] && (j - i < 3 || isPalindrome[i + 1][j - 1]))Palindrome condition using smaller substring info
isPalindrome[i][j] = trueMark substring as palindrome
backtrack(s, 0, path, result, isPalindrome);Start backtracking with precomputed palindrome table
if (start == s.size())Base case: full partition found, add to result
for (int end = start; end < s.size(); ++end)Try all substring ends from start
if (isPalindrome[start][end])Check palindrome in O(1) during recursion
path.push_back(s.substr(start, end - start + 1));Add palindrome substring to current path
backtrack(s, end + 1, path, result, isPalindrome);Recurse for remaining substring
path.pop_back();Backtrack to explore other partitions
function 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"));
Line Notes
const n = s.lengthStore length of string for convenience
const isPalindrome = Array.from({ length: n }, () => Array(n).fill(false));Initialize 2D array for palindrome info
for (let i = n - 1; i >= 0; i--)Fill palindrome table bottom-up for reuse
for (let j = i; j < n; j++)Check all substrings starting at i
if (s[i] === s[j] && (j - i < 3 || isPalindrome[i + 1][j - 1]))Palindrome condition using smaller substring info
isPalindrome[i][j] = trueMark substring as palindrome
function backtrack(start, path)Backtracking function uses precomputed palindrome table
if (start === n)Base case: reached end, add current partition to results
for (let end = start; end < n; end++)Try all substring ends from start
if (isPalindrome[start][end])Check palindrome in O(1) during recursion
path.push(s.slice(start, end + 1))Add palindrome substring to current path
backtrack(end + 1, path)Recurse for remaining substring
path.pop()Backtrack to explore other partitions
TimeO(N * 2^N)
SpaceO(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.
💡 For n=16, this approach is much faster than brute force because palindrome checks are instant, making it practical for interview constraints.
Interview Verdict: Accepted - practical and efficient for interview use
This approach balances clarity and efficiency, making it a strong candidate for coding in interviews.