diff --git a/Dynamic Programming/DP on Strings/Palindrome Partitioning 2/Markdown.md b/Dynamic Programming/DP on Strings/Palindrome Partitioning 2/Markdown.md new file mode 100644 index 00000000..0cd24f28 --- /dev/null +++ b/Dynamic Programming/DP on Strings/Palindrome Partitioning 2/Markdown.md @@ -0,0 +1,50 @@ +# Problem — Palindrome Partitioning II + +## Problem Statement +Given a string `s`, partition `s` such that every substring of the partition is a **palindrome**. +Return the **minimum number of cuts** needed to partition `s` in this way. + + +## Key Insights & Approach + +1. **Dynamic Programming over string indices** + - Let `DP[i]` = minimum number of cuts needed for substring `s[0..i]`. + - Transition: for each `j < i`, if `s[j+1..i]` is a palindrome, + `DP[i] = min(DP[i], DP[j] + 1)`. + - If `s[0..i]` itself is a palindrome → `DP[i] = 0`. + +2. **Palindrome checking optimization** + - Precompute a **2D boolean table** `isPalindrome[i][j]` to check if `s[i..j]` is palindrome in O(1). + - `isPalindrome[i][j] = true` if `s[i] == s[j]` and (`j - i <= 2` or `isPalindrome[i+1][j-1] == true`). + +3. **Final answer** + - `DP[n-1]` gives the **minimum number of cuts** for the entire string `s`. + + + +## Example Cases + +| Example | Input | Output | Explanation | +|-------- |------------|--------|------------------------------------------------| +| 1 | `"aab"` | `1` | Partition: `"aa" | "b"` | +| 2 | `"a"` | `0` | Single character is palindrome → no cut needed | +| 3 | `"abccba"` | `0` | The whole string is palindrome → no cut needed | +| 4 | `"abbab"` | `1` | Partition: `"abba" | "b"` | + +--- + +## Time & Space Complexity + +| Metric | Complexity | Reasoning | +|-----------|------------|--------------------------------------------------------------------| +| **Time** | O(n²) | Precompute palindrome table O(n²) + DP over n with inner loop O(n) | +| **Space** | O(n²) | 2D table for palindrome check + 1D DP array | + +--- + + +### Notes + +- This problem is a **classic string DP / interval DP** problem. +- Precomputing the palindrome table allows for O(1) palindrome checks inside the DP transition. +- Keep the solution **atomic** and modular if you plan to use in multiple related problems. diff --git a/Dynamic Programming/DP on Strings/Palindrome Partitioning 2/palindrome.cpp b/Dynamic Programming/DP on Strings/Palindrome Partitioning 2/palindrome.cpp new file mode 100644 index 00000000..e4275b72 --- /dev/null +++ b/Dynamic Programming/DP on Strings/Palindrome Partitioning 2/palindrome.cpp @@ -0,0 +1,53 @@ +#include +using namespace std; + +class Solution { +public: + int minCut(string s) { + // If the string is empty, return 0 cuts. + if (s.empty()) return 0; + + int n = s.size(); + + // isPalindrome[i][j] indicates whether substring s[i...j] is a palindrome + vector> isPalindrome(n, vector(n, true)); + + // Build the palindrome lookup table using dynamic programming + // Start from the end of string and work backwards + for (int start = n - 1; start >= 0; --start) { + for (int end = start + 1; end < n; ++end) { + // A substring is palindrome if: + // 1. First and last characters match + // 2. Inner substring is also a palindrome (or length <= 2) + isPalindrome[start][end] = (s[start] == s[end]) && isPalindrome[start + 1][end - 1]; + } + } + + // minCuts[i] represents minimum cuts needed for substring s[0...i] + vector minCuts(n); + + // Initialize: worst case is to cut between every character + for (int i = 0; i < n; ++i) { + minCuts[i] = i; // Maximum i cuts needed for string of length i+1 + } + + // Calculate minimum cuts for each position + for (int end = 1; end < n; ++end) { + for (int start = 0; start <= end; ++start) { + // If s[start...end] is a palindrome + if (isPalindrome[start][end]) { + if (start == 0) { + // Entire substring from beginning is palindrome, no cuts needed + minCuts[end] = 0; + } else { + // Add one cut after position (start-1) + minCuts[end] = min(minCuts[end], minCuts[start - 1] + 1); + } + } + } + } + + // Return minimum cuts for entire string + return minCuts[n - 1]; + } +}; diff --git a/Dynamic Programming/DP on Strings/Palindrome Partitioning 2/palindrome.java b/Dynamic Programming/DP on Strings/Palindrome Partitioning 2/palindrome.java new file mode 100644 index 00000000..bfa9ba53 --- /dev/null +++ b/Dynamic Programming/DP on Strings/Palindrome Partitioning 2/palindrome.java @@ -0,0 +1,62 @@ +class Solution +{ + public int minCut(String s) + { + // If the string is empty, return 0 cuts. + if (s.length() == 0) return 0; + + int n = s.length(); + + // isPalindrome[i][j] indicates whether substring s[i...j] is a palindrome + boolean[][] isPalindrome = new boolean[n][n]; + + // Initialize all entries as true + for (boolean[] row : isPalindrome) + { + Arrays.fill(row, true); + } + + // Build palindrome table using dynamic programming + // Start from the end and work backwards to ensure smaller subproblems are solved first + for (int start = n - 1; start >= 0; start--) + { + for (int end = start + 1; end < n; end++) + { + // A substring is a palindrome if: + // 1. First and last characters match + // 2. The substring between them is also a palindrome + isPalindrome[start][end] = (s.charAt(start) == s.charAt(end)) + && isPalindrome[start + 1][end - 1]; + } + } + + // minCuts[i] represents the minimum cuts needed for substring s[0...i] + int[] minCuts = new int[n]; + + // Initialize with worst case: cut after every character + for (int i = 0; i < n; i++) + { + minCuts[i] = i; + } + + // Calculate minimum cuts for each position + for (int end = 1; end < n; end++) + { + // Check all possible starting positions for the last palindrome partition + for (int start = 0; start <= end; start++) + { + // If s[start...end] is a palindrome + if (isPalindrome[start][end]) + { + // If the palindrome starts at index 0, no cuts needed for this substring + // Otherwise, we need 1 cut plus the minimum cuts for s[0...start-1] + minCuts[end] = Math.min(minCuts[end], + start > 0 ? 1 + minCuts[start - 1] : 0); + } + } + } + + // Return minimum cuts needed for the entire string + return minCuts[n - 1]; + } +}