Palindrome Partitioning Using Backtracking in DSA C - Time & Space Complexity
We want to understand how the time needed to find all palindrome partitions grows as the input string gets longer.
Specifically, how does the backtracking process scale with the string size?
Analyze the time complexity of the following code snippet.
bool isPalindrome(char* s, int start, int end) {
while (start < end) {
if (s[start] != s[end]) return false;
start++;
end--;
}
return true;
}
void backtrack(char* s, int start, int len) {
if (start == len) {
// store current partition
return;
}
for (int end = start; end < len; end++) {
if (isPalindrome(s, start, end)) {
backtrack(s, end + 1, len);
}
}
}
This code finds all ways to split the string into palindrome parts using backtracking.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: The for-loop tries every possible substring start to end.
- How many times: For each start position, it checks all ends, and calls backtrack recursively for the rest.
- Palindrome check: Each substring check scans characters between start and end.
- Dominant operation: The recursive calls combined with palindrome checks cause exponential growth.
As the string length grows, the number of palindrome partitions grows very fast.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | Thousands of checks and recursive calls |
| 100 | Millions to billions of operations |
| 1000 | Operations grow beyond practical limits |
Pattern observation: The work grows exponentially because each position can split in many ways, and palindrome checks add extra cost.
Time Complexity: O(n * 2^n)
This means the time grows very fast, roughly doubling with each added character, multiplied by the cost to check palindromes.
[X] Wrong: "The algorithm runs in polynomial time because it just loops over the string."
[OK] Correct: The recursive splitting creates many branches, causing exponential growth, not just simple loops.
Understanding this complexity helps you explain why backtracking can be slow and when to expect exponential time, a key skill in problem solving.
"What if we used memoization to store palindrome checks? How would the time complexity change?"
Practice
s?Solution
Step 1: Understand palindrome partitioning
Palindrome partitioning means dividing the string into parts where each part reads the same forwards and backwards.Step 2: Role of backtracking
Backtracking helps explore all possible ways to split the string into palindrome substrings.Final Answer:
To split s into all possible substrings where each substring is a palindrome -> Option BQuick Check:
Palindrome partitioning = all palindrome substrings [OK]
- Confusing palindrome partitioning with longest palindrome substring
- Thinking it only counts palindromes instead of listing partitions
- Assuming it reverses the string
s starting at index start?Solution
Step 1: Identify when to stop recursion
Whenstartreaches the length of the string, it means the entire string is partitioned.Step 2: What to do at base case
At this point, add the current list of palindrome substrings to the result list and return to explore other partitions.Final Answer:
if (start == s.length()) add current partition to result and return; -> Option AQuick Check:
Base case adds partition when start reaches string end [OK]
- Returning true/false instead of adding partition
- Checking start > length instead of == length
- Using invalid index checks like start < 0
"aab" using palindrome partitioning with backtracking?Solution
Step 1: Check all palindrome substrings
"a" is palindrome, "aa" is palindrome, "b" is palindrome, but "ab" and "aab" are not.Step 2: List all palindrome partitions
Partitions are ["a", "a", "b"] and ["aa", "b"].Final Answer:
[["a", "a", "b"], ["aa", "b"]] -> Option DQuick Check:
Valid palindrome partitions = [["a", "a", "b"], ["aa", "b"]] [OK]
- Including non-palindrome substrings like "ab"
- Missing the partition ["aa", "b"]
- Returning the whole string as one partition
void backtrack(char* s, int start, char** current, int currentSize, char*** result, int* returnSize) {
if (start == strlen(s)) {
result[*returnSize] = current;
(*returnSize)++;
return;
}
for (int end = start; end < strlen(s); end++) {
if (isPalindrome(s, start, end)) {
current[currentSize] = substring(s, start, end);
backtrack(s, end + 1, current, currentSize + 1, result, returnSize);
}
}
}Solution
Step 1: Analyze base case assignment
Assigningcurrentpointer directly toresult[*returnSize]stores the same reference for all partitions.Step 2: Understand backtracking data handling
We must copy the current partition array to avoid overwriting when backtracking continues.Final Answer:
Assigning current directly to result causes shared reference issues -> Option AQuick Check:
Copy partitions to result to avoid shared references [OK]
- Not copying current partition array
- Incorrect loop range for end index
- Ignoring base case correctness
s of length n?Solution
Step 1: Understand palindrome checking cost
Checking palindrome by reversing substring each time costs O(n) per check, leading to O(n^3) overall.Step 2: Use dynamic programming optimization
Precompute a 2D tabledpwheredp[i][j]stores palindrome status for substrings[i..j]. This reduces palindrome checks to O(1).Step 3: Benefit in backtracking
Usingdpspeeds up backtracking by avoiding repeated palindrome checks.Final Answer:
Precompute a 2D boolean table dp where dp[i][j] is true if substring s[i..j] is palindrome -> Option CQuick Check:
DP table for palindrome checking optimizes backtracking [OK]
- Reversing substring every time causing slow performance
- Using hash map adds unnecessary overhead
- Skipping palindrome checks leads to wrong partitions
Ready to prove it?
Pass the test and this pattern moves unknown
