Bird
Raised Fist0
Interview PrepbacktrackingmediumAmazonGoogleFacebook

Palindrome Partitioning

Choose your preparation mode4 modes available

Start learning this pattern below

Jump into concepts and practice - no test required

or
Recommended
Test this pattern10 questions across easy, medium, and hard to know if this pattern is strong
🎯
Palindrome Partitioning
mediumBACKTRACKINGAmazonGoogleFacebook

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.

💡 This problem requires exploring all possible ways to split a string into substrings that are palindromes. Beginners often struggle because it involves recursion with backtracking and pruning, which can be tricky to implement and understand. Think of it like trying every possible way to cut a string into pieces, but only keeping those pieces that read the same forwards and backwards.
📋
Problem Statement

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
💡
Example
Input"aab"
Outputaabaab

The 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
⚠️
Common Mistakes
Not backtracking properly (forgetting to pop from path)

Leads to incorrect partitions or duplicates in results

Always pop the last added substring after recursive call returns

Checking palindrome inefficiently inside recursion repeatedly

Causes timeouts or slow performance

Memoize palindrome checks or precompute palindrome table

Not handling empty string or single character edge cases

May cause errors or miss valid partitions

Add base case for empty string and handle single characters as palindromes

Modifying shared path list without copying when adding to results

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

Using substring indices incorrectly (off-by-one errors)

Incorrect substrings lead to wrong partitions or runtime errors

Carefully use substring methods with correct start and end indices

🧠
Brute Force (Pure Recursion with Palindrome Check)
💡 This approach is the foundation: it tries every possible partition and checks if each substring is a palindrome. It helps understand the problem's exponential nature and the need for pruning. Imagine trying all ways to cut the string and only keeping those cuts where each piece is a palindrome.

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

  1. Start from index 0 and try all substrings s[start:end].
  2. Check if the substring is a palindrome.
  3. If yes, add it to the current partition and recurse from end index.
  4. When start reaches the end of the string, add the current partition to results.
💡 The challenge is to generate all partitions and prune invalid ones early by palindrome checks. This approach shows the brute force nature and why pruning is important.
</>
Code
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"))
Line Notes
def is_palindrome(sub):Helper function to check palindrome property of substring
return sub == sub[::-1]Simple palindrome check by reversing string
def backtrack(start, path):Recursive function exploring partitions from index start
if start == len(s):Base case: reached end of string, add current partition to results
for end in range(start + 1, len(s) + 1):Try all possible substring ends from start
substr = s[start:end]Extract substring to check
if is_palindrome(substr):Only proceed if substring is palindrome to prune search
path.append(substr)Choose this substring and add to current path
backtrack(end, path)Recurse to partition remaining string
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) {
        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"));
    }
}
Line Notes
public List<List<String>> partition(String s)Main function to start partitioning
backtrack(s, 0, new ArrayList<>(), result);Start recursion from index 0 with empty path
if (start == s.length())Base case: full partition found, add copy to result
for (int end = start + 1; end <= s.length(); end++)Try all substring ends from start
String substr = s.substring(start, end);Extract substring to check
if (isPalindrome(substr))Prune search by checking palindrome validity
path.add(substr);Choose substring and add to current partition path
backtrack(s, end, path, result);Recurse for remaining string
path.remove(path.size() - 1);Backtrack to explore other partitions
private boolean isPalindrome(String str)Helper to check palindrome by two pointers
while (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;
}
Line Notes
vector<vector<string>> partition(string s)Main function to initiate backtracking
backtrack(s, 0, path, result);Start recursion from index 0 with empty path
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(s, start, end))Check palindrome to prune invalid partitions
path.push_back(s.substr(start, end - start + 1));Add current palindrome substring to path
backtrack(s, end + 1, path, result);Recurse for remaining substring
path.pop_back();Backtrack to try other partitions
bool isPalindrome(const string& s, int left, int right)Helper to check palindrome using two pointers
while (left < right)Compare characters inward from both ends
function 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"));
Line Notes
function isPalindrome(sub)Helper function to check if substring is palindrome
let left = 0, right = sub.length - 1;Initialize pointers for palindrome check
while (left < right)Two-pointer palindrome check moving inward
if (sub[left++] !== sub[right--]) return false;Return false immediately if mismatch found
function backtrack(start, path)Recursive function to explore partitions from start
if (start === s.length)Base case: reached end, add current partition copy to result
for (let end = start + 1; end <= s.length; end++)Try all substring ends from start
const substr = s.slice(start, end);Extract substring to check
if (isPalindrome(substr))Prune search by palindrome validation
path.push(substr)Add substring to current partition path
backtrack(end, path)Recurse for remaining string
path.pop()Backtrack to try other partitions
result.push([...path])Add a copy of current partition to results
Complexity
TimeO(N * 2^N)
SpaceO(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.

💡 For n=10, this means roughly 10 * 2^10 = 10,240 operations, which is feasible, but for n=16 it grows exponentially and becomes slow.
Interview Verdict: Use only to introduce - too slow for large inputs

This approach is simple but inefficient; it helps understand the problem but is impractical for large strings due to exponential time.

🧠
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

  1. Precompute a 2D boolean table is_palindrome[i][j] indicating if s[i:j] is palindrome.
  2. Use backtracking to generate partitions, but check palindrome validity from the table in O(1).
  3. Add valid palindrome substrings to current path and recurse.
  4. Add completed partitions to results when end of string is reached.
💡 Precomputing palindrome info upfront reduces repeated substring checks during recursion, improving efficiency.
</>
Code
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
Complexity
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.

🧠
Backtracking with Dynamic Palindrome Check (Expand Around Center)
💡 Instead of precomputing all palindromes, this approach checks palindrome substrings on the fly by expanding around centers, reducing memory usage. It trades some speed for lower memory, which can be useful in memory-constrained environments.

Intuition

At each recursion step, expand around the current substring to check palindrome validity without a large DP table, then recurse if valid.

Algorithm

  1. At each recursion, try all substrings starting at current index.
  2. Check palindrome by expanding around substring boundaries.
  3. If palindrome, add substring to path and recurse.
  4. Add completed partitions to results when end of string is reached.
💡 This approach trades some speed for lower memory by avoiding a large DP table, still pruning invalid partitions early.
</>
Code
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"))
Line Notes
def is_palindrome(left, right):Check palindrome by expanding around substring boundaries
while left < right:Compare characters inward from both ends
if s[left] != s[right]:Return False immediately if mismatch found
left += 1Move left pointer inward
right -= 1Move right pointer inward
return TrueSubstring is palindrome if no mismatches found
def backtrack(start, path):Recursive function to explore partitions
if start == len(s):Base case: reached end, add current partition copy to result
for end in range(start, len(s))Try all substring ends from start
if is_palindrome(start, end)Check palindrome dynamically before recursion
path.append(s[start:end+1])Add 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) {
        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"));
    }
}
Line Notes
private void backtrack(String s, int start, List<String> path, List<List<String>> result)Recursive function exploring partitions
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(s, start, end))Check palindrome dynamically before recursion
path.add(s.substring(start, end + 1));Add palindrome substring to current path
backtrack(s, end + 1, path, result);Recurse for remaining substring
path.remove(path.size() - 1);Backtrack to explore other partitions
private boolean isPalindrome(String s, int left, int right)Check palindrome by expanding around substring boundaries
while (left < right)Compare characters inward from both ends
if (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;
}
Line Notes
void backtrack(const string& s, int start, vector<string>& path, vector<vector<string>>& result)Recursive function exploring partitions
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(s, start, end))Check palindrome dynamically before recursion
path.push_back(s.substr(start, end - start + 1));Add palindrome substring to current path
backtrack(s, end + 1, path, result);Recurse for remaining substring
path.pop_back();Backtrack to explore other partitions
bool isPalindrome(const string& s, int left, int right)Check palindrome by expanding around substring boundaries
while (left < right)Compare characters inward from both ends
if (s[left++] != s[right--]) return false;Return false immediately if mismatch
function 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"));
Line Notes
function isPalindrome(left, right)Check palindrome by expanding around substring boundaries
while (left < right)Compare characters inward from both ends
if (s[left] !== s[right]) return false;Return false immediately if mismatch
left++Move left pointer inward
right--Move right pointer inward
return trueSubstring is palindrome if no mismatches found
function backtrack(start, path)Recursive function exploring partitions
if (start === s.length)Base case: full partition found, add copy to result
for (let end = start; end < s.length; end++)Try all substring ends from start
if (isPalindrome(start, end))Check palindrome dynamically before 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
Complexity
TimeO(N * 2^N)
SpaceO(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 uses less memory than precomputing all palindromes but may be slower for large inputs due to repeated palindrome checks.
Interview Verdict: Accepted - good memory tradeoff, slightly slower than memoized palindrome

This approach is useful when memory is limited but still efficient enough for typical interview constraints.

📊
All Approaches - One-Glance Tradeoffs
💡 Approach 2 (Backtracking with Palindrome Memoization) is recommended for most interviews due to its balance of clarity and efficiency.
ApproachTimeSpaceStack RiskReconstructUse In Interview
1. Brute ForceO(N * 2^N)O(N)Yes (deep recursion)YesMention only - never code
2. Backtracking with Palindrome MemoizationO(N * 2^N)O(N^2)Yes (deep recursion)YesCode this approach in interviews
3. Backtracking with Dynamic Palindrome CheckO(N * 2^N)O(N)Yes (deep recursion)YesMention as memory-optimized alternative
💼
Interview Strategy
💡 Use this guide to understand the problem deeply before interviews. Start by clarifying the problem, then explain brute force, optimize palindrome checks, and finally discuss tradeoffs.

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

Clarify: 2min → Approach: 5min → Code: 10min → Test: 3min. Total ~20min

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.
💡 These follow-ups test your ability to extend the problem and optimize further, common in FAANG interviews.
🔍
Pattern Recognition

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.

Signature Phrases

'partition s such that every substring is a palindrome''return all possible palindrome partitioning'

NOT This Pattern When

Problems asking only for count of partitions or minimum cuts without generating all partitions

Similar Problems

Palindrome Partitioning II - focuses on minimum cuts instead of all partitionsRestore IP Addresses - partitions string into valid IP segmentsWord Break II - partitions string into dictionary words

Practice

(1/5)
1. You need to count all distinct ways to place n queens on an nxn chessboard so that no two queens threaten each other. Which algorithmic approach guarantees finding all valid solutions efficiently by exploring partial placements and pruning invalid ones early?
easy
A. Backtracking that tries all column placements per row and abandons invalid partial boards
B. Greedy algorithm that places queens row by row choosing the first safe column
C. Dynamic programming that stores counts of valid queen placements per row without conflict checks
D. Breadth-first search enumerating all board states level by level

Solution

  1. Step 1: Understand problem constraints

    The problem requires counting all valid queen placements with no conflicts, which demands exploring all possibilities.
  2. Step 2: Identify suitable algorithm

    Backtracking systematically tries each column per row and abandons partial solutions that violate constraints, ensuring correctness and pruning search space.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Backtracking explores all valid configurations efficiently [OK]
Hint: Counting all valid placements requires backtracking [OK]
Common Mistakes:
  • Assuming greedy placement always works
  • Believing DP without conflict checks suffices
2. You need to find the k-th lexicographically smallest permutation of numbers from 1 to n. Which approach guarantees an optimal solution without generating all permutations explicitly?
easy
A. Use a greedy algorithm that swaps elements to reach the k-th permutation directly.
B. Compute factorial values to determine each digit's position and build the permutation directly.
C. Use dynamic programming to count permutations and reconstruct the k-th sequence.
D. Generate all permutations using backtracking and return the k-th one.

Solution

  1. Step 1: Understand the problem constraints

    The problem requires finding the k-th permutation without enumerating all permutations, which is infeasible for large n.
  2. Step 2: Identify the approach that uses factorial number system

    Using precomputed factorials, we can determine the index of each digit in the permutation directly, avoiding full enumeration.
  3. Final Answer:

    Option B -> Option B
  4. Quick Check:

    Factorial-based direct computation is optimal and avoids timeouts [OK]
Hint: Factorials let you jump directly to the k-th permutation [OK]
Common Mistakes:
  • Thinking greedy swaps can find k-th permutation efficiently
  • Assuming DP is needed here
  • Trying to generate all permutations for large n
3. What is the time complexity of the bitmask-optimized backtracking solution for the N-Queens problem, and why is the common misconception that it is O(n^3) incorrect?
medium
A. O(n^3) because there are three nested loops over rows, columns, and diagonals
B. O(2^n) because bitmasking iterates over all subsets of columns
C. O(n!) because each row places one queen and pruning reduces the search space factorially
D. O(n^2) because each queen placement checks all previous rows and columns

Solution

  1. Step 1: Identify the branching factor per row

    Each row places exactly one queen, and pruning avoids invalid columns and diagonals, reducing choices drastically.
  2. Step 2: Understand factorial growth

    Because queens cannot share columns, the number of ways to place queens is at most n!; pruning reduces this further but worst-case remains O(n!).
  3. Final Answer:

    Option C -> Option C
  4. Quick Check:

    Bitmask pruning reduces search to permutations of columns [OK]
Hint: N-Queens search space is permutations, not polynomial loops [OK]
Common Mistakes:
  • Assuming nested loops imply cubic time
  • Confusing bitmask subsets with full subsets
  • Ignoring pruning effect
4. Examine the following snippet from an optimized Sudoku solver. Which line contains a subtle bug that can cause incorrect solutions or failure to backtrack properly?
def backtrack():
    if not empties:
        return True
    empties.sort(key=lambda x: len(get_candidates(x[0], x[1])))
    r, c = empties[0]
    for ch in get_candidates(r, c):
        board[r][c] = ch
        rows[r].add(ch)
        cols[c].add(ch)
        boxes[(r//3)*3 + c//3].add(ch)
        empties.pop(0)
        if backtrack():
            return True
        # Undo changes
        board[r][c] = '.'
        rows[r].remove(ch)
        cols[c].remove(ch)
        boxes[(r//3)*3 + c//3].remove(ch)
        empties.insert(0, (r, c))
    return False
medium
A. Line that pops the first empty cell from empties
B. Line that sorts empties by candidate count
C. Line that adds ch to rows[r]
D. Line that resets board[r][c] to '.' after backtracking

Solution

  1. Step 1: Identify mutation of empties list

    The line popping empties[0] removes the cell but is never restored on backtrack failure.
  2. Step 2: Consequence of missing restoration

    Without re-adding the cell to empties after failed recursion, future calls miss this cell, causing incorrect state and solutions.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Backtracking requires undoing all changes including empties list [OK]
Hint: Backtracking must undo all state changes, including empties list [OK]
Common Mistakes:
  • Forgetting to restore empties after pop
  • Assuming board reset is sufficient
5. Suppose the Word Search problem is modified so that the same cell can be used multiple times in forming the word. Which modification to the backtracking algorithm correctly handles this change?
hard
A. Add a global visited set to track cells used in all paths
B. Keep marking visited cells but reset them after each recursion
C. Remove the in-place marking of visited cells to allow reuse
D. Use dynamic programming to store partial results and avoid recomputation

Solution

  1. Step 1: Understand new constraint

    Cells can be reused multiple times, so marking visited cells to prevent reuse is no longer needed.
  2. Step 2: Modify algorithm accordingly

    Removing in-place marking allows revisiting cells, correctly reflecting the relaxed constraint.
  3. Step 3: Evaluate other options

    Keeping marking or using global visited set prevents reuse incorrectly; DP is not suitable here.
  4. Final Answer:

    Option C -> Option C
  5. Quick Check:

    Removing visited marking enables cell reuse as required [OK]
Hint: Relaxing constraints often means removing pruning steps [OK]
Common Mistakes:
  • Keeping visited marking, causing false negatives
  • Adding unnecessary global state