diff --git a/dynamic_programming/palindrome_partitioning.cpp b/dynamic_programming/palindrome_partitioning.cpp index 9b69a125e11..fa7f1071308 100644 --- a/dynamic_programming/palindrome_partitioning.cpp +++ b/dynamic_programming/palindrome_partitioning.cpp @@ -2,25 +2,40 @@ * @file * @brief Implements [Palindrome * Partitioning](https://leetcode.com/problems/palindrome-partitioning-ii/) - * algorithm, giving you the minimum number of partitions you need to make + * algorithm, giving the minimum number of cuts to partition a string into + * palindromes. * * @details - * palindrome partitioning uses dynamic programming and goes to all the possible - * partitions to find the minimum you are given a string and you need to give - * minimum number of partitions needed to divide it into a number of palindromes - * [Palindrome Partitioning] - * (https://www.geeksforgeeks.org/palindrome-partitioning-dp-17/) overall time - * complexity O(n^2) For example: example 1:- String : "nitik" Output : 2 => "n - * | iti | k" For example: example 2:- String : "ababbbabbababa" Output : 3 => - * "aba | b | bbabb | ababa" - * @author [Sujay Kaushik] (https://github.com/sujaykaushik008) + * Given a string, find the minimum number of cuts needed to partition it + * such that every substring is a palindrome. + * + * ### Algorithm + * 1. Build a 2D boolean table `is_palindrome[i][j]` in O(n^2) to check + * if any substring is a palindrome in O(1). + * 2. Use a 1D DP array `cuts[i]` = minimum cuts for str[0..i]. + * For each position i, scan all j <= i: if str[j..i] is a palindrome, + * then cuts[i] = min(cuts[i], cuts[j-1] + 1). + * + * This achieves true O(n^2) time — the previous implementation used a 2D + * cuts table requiring a third nested loop, making it O(n^3) despite the + * O(n^2) claim in the documentation. + * + * Time Complexity: O(n^2) — genuine, verified + * Space Complexity: O(n^2) for palindrome table + O(n) for cuts array + * + * Example 1: "nitik" → 2 cuts → "n | iti | k" + * Example 2: "ababbbabbababa" → 3 cuts → "aba|b|bbabb|ababa" + * + * @author [Sujay Kaushik](https://github.com/sujaykaushik008) + * @author [https-hari](https://github.com/https-hari) */ -#include // for std::min -#include // for std::assert -#include // for INT_MAX -#include // for io operations -#include // for std::vector +#include /// for std::min +#include /// for assert +#include /// for INT_MAX +#include /// for std::cout +#include /// for std::string +#include /// for std::vector /** * @namespace dynamic_programming @@ -30,93 +45,92 @@ namespace dynamic_programming { /** * @namespace palindrome_partitioning - * @brief Functions for [Palindrome - * Partitioning](https://leetcode.com/problems/palindrome-partitioning-ii/) - * algorithm + * @brief Functions for Palindrome Partitioning algorithm */ namespace palindrome_partitioning { /** - * Function implementing palindrome partitioning algorithm using lookup table - * method. - * @param str input string - * @returns minimum number of partitions + * @brief Computes minimum cuts to partition string into palindromes. + * + * @details + * Fixes O(n^3) implementation by replacing 2D cuts table with a 1D + * DP array, eliminating the innermost partition loop entirely. + * + * Previous approach: cuts[i][j] required iterating all partition points k + * between i and j → three nested loops → O(n^3) + * Current approach: cuts[i] = min cuts for str[0..i] + * one scan using precomputed palindrome table → O(n^2) + * + * @param str input string to partition + * @returns minimum number of cuts needed */ -int pal_part(const std::string &str) { - int n = str.size(); +int pal_part(const std::string& str) { + int n = static_cast(str.size()); + if (n == 0) return 0; - // creating lookup table for minimum number of cuts - std::vector > cuts(n, std::vector(n, 0)); + // Step 1: Build palindrome lookup table in O(n^2) + // is_palindrome[i][j] = true if str[i..j] is a palindrome + std::vector> is_palindrome( + n, std::vector(n, false)); - // creating lookup table for palindrome checking - std::vector > is_palindrome(n, - std::vector(n, false)); + for (int i = 0; i < n; i++) + is_palindrome[i][i] = true; // single char is always palindrome - // initialization - for (int i = 0; i < n; i++) { - is_palindrome[i][i] = true; - cuts[i][i] = 0; + for (int len = 2; len <= n; len++) { + for (int i = 0; i <= n - len; i++) { + int j = i + len - 1; + if (len == 2) + is_palindrome[i][j] = (str[i] == str[j]); + else + is_palindrome[i][j] = + (str[i] == str[j]) && is_palindrome[i + 1][j - 1]; + } } - for (int len = 2; len <= n; len++) { - for (int start_index = 0; start_index < n - len + 1; start_index++) { - int end_index = start_index + len - 1; - - if (len == 2) { - is_palindrome[start_index][end_index] = - (str[start_index] == str[end_index]); - } else { - is_palindrome[start_index][end_index] = - (str[start_index] == str[end_index]) && - is_palindrome[start_index + 1][end_index - 1]; - } + // Step 2: 1D DP — cuts[i] = min cuts for str[0..i] + // Eliminates the third nested loop from the previous O(n^3) approach + std::vector cuts(n, INT_MAX); - if (is_palindrome[start_index][end_index]) { - cuts[start_index][end_index] = 0; - } else { - cuts[start_index][end_index] = INT_MAX; - for (int partition = start_index; partition <= end_index - 1; - partition++) { - cuts[start_index][end_index] = - std::min(cuts[start_index][end_index], - cuts[start_index][partition] + - cuts[partition + 1][end_index] + 1); + for (int i = 0; i < n; i++) { + if (is_palindrome[0][i]) { + cuts[i] = 0; // whole prefix is a palindrome, no cuts needed + } else { + for (int j = 1; j <= i; j++) { + if (is_palindrome[j][i] && cuts[j - 1] != INT_MAX) { + cuts[i] = std::min(cuts[i], cuts[j - 1] + 1); } } } } - return cuts[0][n - 1]; + return cuts[n - 1]; } + } // namespace palindrome_partitioning } // namespace dynamic_programming /** * @brief Test Function - * @return void + * @returns void */ static void test() { - // custom input vector - std::vector custom_input{"nitik", "ababbbabbababa", "abdc"}; + // basic cases from original file + assert(dynamic_programming::palindrome_partitioning::pal_part("nitik") == 2); + assert(dynamic_programming::palindrome_partitioning::pal_part("ababbbabbababa") == 3); + assert(dynamic_programming::palindrome_partitioning::pal_part("abdc") == 3); - // calculated output vector by pal_part Function - std::vector calculated_output(3); + // edge cases + assert(dynamic_programming::palindrome_partitioning::pal_part("") == 0); + assert(dynamic_programming::palindrome_partitioning::pal_part("a") == 0); + assert(dynamic_programming::palindrome_partitioning::pal_part("aa") == 0); + assert(dynamic_programming::palindrome_partitioning::pal_part("ab") == 1); - for (int i = 0; i < 3; i++) { - calculated_output[i] = - dynamic_programming::palindrome_partitioning::pal_part( - custom_input[i]); - } + // whole string is palindrome — 0 cuts + assert(dynamic_programming::palindrome_partitioning::pal_part("racecar") == 0); + assert(dynamic_programming::palindrome_partitioning::pal_part("aaa") == 0); - // expected output vector - std::vector expected_output{2, 3, 3}; - - // Testing implementation via assert function - // It will throw error if any of the expected test fails - // Else it will give nothing - for (int i = 0; i < 3; i++) { - assert(expected_output[i] == calculated_output[i]); - } + // worst case — no palindrome substrings longer than 1 + assert(dynamic_programming::palindrome_partitioning::pal_part("abcd") == 3); std::cout << "All tests passed successfully!\n"; } @@ -126,6 +140,6 @@ static void test() { * @returns 0 on exit */ int main() { - test(); // execute the test + test(); return 0; -} +} \ No newline at end of file