1+ class PalindromePartitioning {
2+
3+ /**
4+ * Calculates the minimum number of cuts needed to partition a string such that every
5+ * substring of the partition is a palindrome.
6+ *
7+ * ALGORITHM DESCRIPTION:
8+ * --------------------
9+ * This problem asks for the minimum cuts, which suggests an optimization problem that is
10+ * well-suited for Dynamic Programming. The solution involves two main DP components.
11+ *
12+ * STAGE 1: PRE-COMPUTING ALL PALINDROMIC SUBSTRINGS
13+ * Before we can find the minimum cuts, we need an efficient way to check if any given
14+ * substring is a palindrome. We can pre-compute this information.
15+ *
16+ * State Definition:
17+ * We use a 2D boolean table `isPalindrome[i][j]`, where `isPalindrome[i][j]` is `true`
18+ * if the substring from index `i` to `j` (inclusive) is a palindrome, and `false` otherwise.
19+ *
20+ * Logic:
21+ * - A single character is always a palindrome (`isPalindrome[i][i] = true`).
22+ * - A substring of length > 1 from `i` to `j` is a palindrome if:
23+ * 1. The characters at the ends match (`s.charAt(i) == s.charAt(j)`).
24+ * 2. The inner substring from `i+1` to `j-1` is also a palindrome.
25+ * We can fill this table in O(N^2) time.
26+ *
27+ * STAGE 2: FINDING THE MINIMUM CUTS
28+ * With our palindrome information ready, we can now find the minimum cuts.
29+ *
30+ * State Definition:
31+ * We use a 1D integer array `cuts[i]`, where `cuts[i]` stores the minimum number of
32+ * cuts needed for the prefix of the string of length `i+1` (i.e., `s.substring(0, i+1)`).
33+ *
34+ * Logic:
35+ * - Initialize `cuts[i] = i`, which represents the worst-case scenario where we cut
36+ * after every character (e.g., "abc" -> "a|b|c" needs 2 cuts for a prefix of length 3).
37+ * - We then iterate `i` from 0 to the end of the string. For each `i`, we check all
38+ * substrings ending at `i`. Let's say a substring starts at `j` (where `0 <= j <= i`).
39+ * - If `s.substring(j, i+1)` is a palindrome (which we can check in O(1) from our table):
40+ * - It means the entire segment from `j` to `i` needs 0 cuts.
41+ * - The total cuts needed for the prefix up to `i` would be `1 +` (the minimum cuts
42+ * needed for the prefix ending at `j-1`).
43+ * - We update `cuts[i]` with the minimum value found among all such valid `j`.
44+ * - A special case: if the entire prefix `s.substring(0, i+1)` is a palindrome (`j=0`),
45+ * then `cuts[i]` is 0.
46+ *
47+ * Final Answer: The result is `cuts[s.length() - 1]`.
48+ *
49+ * COMPLEXITY ANALYSIS:
50+ * --------------------
51+ * Let N be the length of the string `s`.
52+ *
53+ * Time Complexity: O(N^2)
54+ * - Stage 1 (building the `isPalindrome` table) takes O(N^2).
55+ * - Stage 2 (building the `cuts` array) has two nested loops, also taking O(N^2).
56+ * - Total time complexity is O(N^2) + O(N^2) = O(N^2).
57+ *
58+ * Space Complexity: O(N^2)
59+ * - The `isPalindrome` table requires O(N^2) space.
60+ * - The `cuts` array requires O(N) space.
61+ * - The dominant factor is the 2D table, so the total space complexity is O(N^2).
62+ *
63+ * @param s The input string.
64+ * @return The minimum number of cuts required.
65+ */
66+ public int minCuts (String s ) {
67+ // Step 1: Handle edge cases.
68+ if (s == null || s .length () <= 1 ) {
69+ return 0 ;
70+ }
71+ int n = s .length ();
72+
73+ // --- STAGE 1: Pre-compute the palindrome table ---
74+ // isPalindrome[i][j] is true if substring s[i..j] is a palindrome.
75+ boolean [][] isPalindrome = new boolean [n ][n ];
76+
77+ for (int len = 1 ; len <= n ; len ++) {
78+ for (int i = 0 ; i <= n - len ; i ++) {
79+ int j = i + len - 1 ;
80+ // Check if characters at ends match and if the inner substring is also a palindrome.
81+ if (s .charAt (i ) == s .charAt (j ) && (len <= 2 || isPalindrome [i + 1 ][j - 1 ])) {
82+ isPalindrome [i ][j ] = true ;
83+ }
84+ }
85+ }
86+
87+ // --- STAGE 2: Calculate the minimum cuts using the palindrome table ---
88+ // cuts[i] = minimum cuts for the prefix s[0..i].
89+ int [] cuts = new int [n ];
90+
91+ for (int i = 0 ; i < n ; i ++) {
92+ // Check if the whole prefix s[0..i] is a palindrome.
93+ if (isPalindrome [0 ][i ]) {
94+ cuts [i ] = 0 ; // 0 cuts needed.
95+ } else {
96+ // Worst-case: cut after every character.
97+ cuts [i ] = i ;
98+ // Find the minimum cuts by checking all possible last palindromic substrings.
99+ // If s[j..i] is a palindrome, then we can make a cut after s[j-1].
100+ for (int j = 1 ; j <= i ; j ++) {
101+ if (isPalindrome [j ][i ]) {
102+ // The number of cuts would be 1 (for the new cut) + cuts[j-1] (for the prefix).
103+ cuts [i ] = Math .min (cuts [i ], 1 + cuts [j - 1 ]);
104+ }
105+ }
106+ }
107+ }
108+
109+ // The final answer is the minimum cuts for the entire string s[0..n-1].
110+ return cuts [n - 1 ];
111+ }
112+
113+ public static void main (String [] args ) {
114+ PalindromePartitioning pp = new PalindromePartitioning ();
115+
116+ // Example 1: "aab" -> "aa" | "b" -> 1 cut
117+ String s1 = "aab" ;
118+ System .out .println ("The minimum cuts for \" " + s1 + "\" is: " + pp .minCuts (s1 )); // Expected: 1
119+
120+ // Example 2: "racecar" is already a palindrome
121+ String s2 = "racecar" ;
122+ System .out .println ("The minimum cuts for \" " + s2 + "\" is: " + pp .minCuts (s2 )); // Expected: 0
123+
124+ // Example 3: A more complex case
125+ String s3 = "ababbbabbababa" ;
126+ System .out .println ("The minimum cuts for \" " + s3 + "\" is: " + pp .minCuts (s3 )); // Expected: 3
127+
128+ // Example 4: All different characters
129+ String s4 = "abcde" ;
130+ System .out .println ("The minimum cuts for \" " + s4 + "\" is: " + pp .minCuts (s4 )); // Expected: 4
131+ }
132+ }
0 commit comments