Daily Dose of DSA - Day 17
Palindromic Partition using Recursion + TDDP (O(n^3) TC & O(n^2) SC) , Recursion+TDDP (O(n^3)TC & O(n^2) SC),using Iteration+BUDP (O(n^3)TC & O(n)SC)
Question Link: https://practice.geeksforgeeks.org/problems/palindromic-patitioning4845/1
Recursion + Brute Force + Slight Memoization (Top-down DP)(TLE)(O(n^3) TC & O(n^2) SC)
// User function Template for C++
typedef long long ll;
class Solution{
public:
ll dp[501][501];
bool isPalindrome(string str, int i, int j) {
while (i < j) {
if (str[i] != str[j])
return false;
i++;
j--;
}
return true;
}
ll rec(string str, int i, int j) {
// Base case: If the substring is already a palindrome, no partition needed
if (i >= j || isPalindrome(str, i, j))
return 0;
// If the subproblem has already been solved, return the result
if (dp[i][j] != -1)
return dp[i][j];
ll ans = INT_MAX;
// Partition the string at every possible position and calculate the minimum cuts
for (int k = i; k < j; k++) {
ll left = rec(str, i, k);
ll right = rec(str, k + 1, j);
ans = min(ans, left + right + 1);
}
// Store the minimum value in the dp table for memoization
return dp[i][j] = ans;
}
int palindromicPartition(string str)
{
// code here
int n = str.length();
memset(dp, -1, sizeof(dp));
return rec(str, 0, n - 1);
}
};
The function
palindromicPartition
initializes thedp
table and calls the recursive functionrec
, which takes O(1) time. Thememset
operation takes O(n^2) time since it fills the entiredp
table.The recursive function
rec
is called for each substring of the input string. There are a total of n^2 substrings in the worst case. This is because there are n choices for the starting index and n choices for the ending index. Therefore, the total number of recursive calls is O(n^2).Inside the
rec
function, there is a loop that partitions the string at every possible position. This loop iterates from i to j-1, where i and j represent the starting and ending indices of the current substring. In the worst case, the length of the substring is n, so the loop runs n times.Inside the loop, the function makes recursive calls to
rec
for the left and right partitions of the substring. These recursive calls are made n times in the worst case since the loop runs n times.Overall, for each recursive call, there is a loop that runs n times, and inside the loop, there are recursive calls made n times. Hence, the worst-case time complexity can be approximated as O(n n n) = O(n^3).
Therefore, the worst-case time complexity of the given solution is O(n^3).
Recursion + Memoization (Top-down DP)(O(n^3) TC & O(n^2) SC)
typedef long long ll;
class Solution {
public:
ll dp[501][501];
// Function to check if a substring is a palindrome
bool isPalindrome(string str, int i, int j) {
while (i < j) {
if (str[i] != str[j])
return false;
i++;
j--;
}
return true;
}
// Recursive function to calculate the minimum cuts
int rec(string str, int i, int j) {
// Base case: If the substring is already a palindrome, no partition needed
if (i >= j || isPalindrome(str, i, j))
return 0;
// If the subproblem has already been solved, return the result from the dp table
if (dp[i][j] != -1)
return dp[i][j];
int ans = INT_MAX;
for (int k = i; k < j; k++) {
int left, right;
// Calculate the minimum cuts for the left and right substrings
if (dp[i][k] != -1)
left = dp[i][k];
else {
left = rec(str, i, k);
dp[i][k] = left; // Memoize the result in the dp table
}
if (dp[k + 1][j] != -1)
right = dp[k + 1][j];
else {
right = rec(str, k + 1, j);
dp[k + 1][j] = right; // Memoize the result in the dp table
}
// Find the minimum cuts among all possible partitions
ans = min(ans, left + right + 1);
}
dp[i][j] = ans; // Store the minimum cuts in the dp table for memoization
return ans;
}
int palindromicPartition(string str) {
int n = str.length();
// Initialize the dp table with -1 (indicating subproblems that haven't been solved yet)
memset(dp, -1, sizeof(dp));
// Call the recursive function to calculate the minimum cuts
return rec(str, 0, n - 1);
}
};
The time complexity of the provided code is O(n^3), where n is the length of the input string.
The recursive function rec
is called for each substring of the input string. For each substring of length n, there are O(n^2) possible substrings. In the worst case, for each substring, the function checks if it is a palindrome, which takes O(n) time. Therefore, the time complexity of the recursive function is O(n^3).Hence, the overall time complexity of the code is O(n^3) in the worst case.
Iteration + Tabulation (Bottom-up DP)(O(n^3) TC & O(n) SC)
typedef long long ll;
class Solution{
public:
ll dp[501];
bool isPalindrome(string str, int i, int j) {
while (i < j) {
if (str[i] != str[j])
return false;
i++;
j--;
}
return true;
}
int palindromicPartition(string str) {
int n = str.length();
memset(dp, 0, sizeof(dp));
for (int i = n - 1; i >= 0; i--) {
dp[i] = INT_MAX;
for (int j = i; j < n; j++) {
if (isPalindrome(str, i, j)) {
if (j == n - 1)
dp[i] = 0; // No partition needed if the substring is already a palindrome
else
dp[i] = min(dp[i], dp[j + 1] + 1); // Partition the string and calculate the minimum cuts
}
}
}
return dp[0];
}
};
The worst-case time complexity of the provided code is O(n^3), where n is the length of the input string. This occurs when the input string is such that all possible substrings need to be checked for palindrome, resulting in a nested loop structure. In the worst case, for each starting index i, the inner loop runs n - i times, and within that, the isPalindrome function checks each substring of length j - i + 1, which can be up to n. Hence, the overall time complexity is O(n^3) in the worst case.
Iteration + Tabulation (Bottom-up DP) (O(n^3)TC & O(n^2)SC)
#include <bits/stdc++.h>
using namespace std;
class Solution {
public:
int palindromicPartition(string str) {
int n = str.length();
// Create a dp table to store the minimum cuts for substrings
vector<vector<int>> dp(n, vector<int>(n, 0));
// Create a boolean table to store the palindrome information for substrings
vector<vector<bool>> isPalindrome(n, vector<bool>(n, false));
// Initialize the dp table and isPalindrome table for substrings of length 1
for (int i = 0; i < n; i++) {
dp[i][i] = 0;
isPalindrome[i][i] = true;
}
// Build the dp table and isPalindrome table bottom-up for substrings of increasing length
for (int len = 2; len <= n; len++) {
for (int i = 0; i <= n - len; i++) {
int j = i + len - 1;
// Check if the current substring is a palindrome
if (str[i] == str[j] && (len == 2 || isPalindrome[i + 1][j - 1]))
isPalindrome[i][j] = true;
// If the substring is a palindrome, no partition needed
if (isPalindrome[i][j])
dp[i][j] = 0;
else {
dp[i][j] = INT_MAX;
// Partition the substring at every possible position and calculate the minimum cuts
for (int k = i; k < j; k++)
dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j] + 1);
}
}
}
// The minimum number of cuts for the entire string is stored in dp[0][n-1]
return dp[0][n - 1];
}
};
The time complexity of the given solution is O(n^3), where n is the length of the input string.
The solution uses a bottom-up dynamic programming (DP) approach to solve the problem. It builds a DP table of size n x n, where each entry dp[i][j] represents the minimum cuts required to partition the substring str[i...j] into palindromic substrings.
The solution iterates over the substrings of increasing length (len) from 2 to n. For each substring, it checks if it is a palindrome using the isPalindrome table, which takes constant time. If the substring is a palindrome, no partition is needed, so dp[i][j] is set to 0. Otherwise, the substring is partitioned at every possible position (k) within the substring, and the minimum cuts are calculated as dp[i][k] + dp[k+1][j] + 1.
Since the solution iterates over substrings of length 2 to n and checks all possible positions for partitioning, the time complexity is O(n^3).
Therefore, the given solution has a time complexity of O(n^3) in the worst case.