🧠
Memoization with Palindrome Preprocessing
💡 Memoization avoids recomputing subproblems by caching results, and precomputing palindrome substrings reduces repeated palindrome checks.
Intuition
Precompute a table to know if s[i..j] is palindrome in O(1). Use recursion with memo to store minimum cuts from index i onward.
Algorithm
- Precompute a 2D boolean table for palindrome substrings.
- Use recursion with memo to find minimum cuts from each index.
- For each index, try all palindrome substrings starting there and recurse.
- Return the cached minimum cuts to avoid recomputation.
💡 Memoization prunes the recursion tree drastically, and palindrome table makes palindrome checks O(1).
Recurrence:dp[i] = min(1 + dp[j+1]) for all j ≥ i where s[i..j] is palindrome
def minCut(s: str) -> int:
n = len(s)
palindrome = [[False]*n for _ in range(n)]
for i in range(n-1, -1, -1):
for j in range(i, n):
if s[i] == s[j] and (j - i < 2 or palindrome[i+1][j-1]):
palindrome[i][j] = True
memo = {}
def dfs(start):
if start == n:
return -1
if start in memo:
return memo[start]
min_cuts = float('inf')
for end in range(start, n):
if palindrome[start][end]:
cuts = 1 + dfs(end + 1)
if cuts < min_cuts:
min_cuts = cuts
memo[start] = min_cuts
return min_cuts
return dfs(0)
Line Notes
palindrome = [[False]*n for _ in range(n)]Initialize 2D table to store palindrome info
for i in range(n-1, -1, -1):Fill palindrome table bottom-up to use smaller substrings
if s[i] == s[j] and (j - i < 2 or palindrome[i+1][j-1])Check palindrome condition using smaller substring
memo = {}Memo dictionary to cache minimum cuts from each start index
if start in memo:Return cached result to avoid recomputation
for end in range(start, n):Try all palindrome substrings starting at start
memo[start] = min_cutsCache computed minimum cuts
public class Solution {
public int minCut(String s) {
int n = s.length();
boolean[][] palindrome = new boolean[n][n];
for (int i = n - 1; i >= 0; i--) {
for (int j = i; j < n; j++) {
if (s.charAt(i) == s.charAt(j) && (j - i < 2 || palindrome[i + 1][j - 1])) {
palindrome[i][j] = true;
}
}
}
Integer[] memo = new Integer[n];
return dfs(s, 0, palindrome, memo);
}
private int dfs(String s, int start, boolean[][] palindrome, Integer[] memo) {
if (start == s.length()) return -1;
if (memo[start] != null) return memo[start];
int minCuts = Integer.MAX_VALUE;
for (int end = start; end < s.length(); end++) {
if (palindrome[start][end]) {
int cuts = 1 + dfs(s, end + 1, palindrome, memo);
if (cuts < minCuts) minCuts = cuts;
}
}
memo[start] = minCuts;
return minCuts;
}
// public static void main(String[] args) {
// Solution sol = new Solution();
// System.out.println(sol.minCut("aab")); // 1
// }
}
Line Notes
boolean[][] palindrome = new boolean[n][n];2D array to store palindrome info
for (int i = n - 1; i >= 0; i--) {Fill palindrome table bottom-up
if (s.charAt(i) == s.charAt(j) && (j - i < 2 || palindrome[i + 1][j - 1])) {Palindrome check using smaller substring
Integer[] memo = new Integer[n];Memo array to cache results
if (memo[start] != null) return memo[start];Return cached result if available
for (int end = start; end < s.length(); end++) {Try all palindrome substrings starting at start
memo[start] = minCuts;Cache computed minimum cuts
#include <iostream>
#include <vector>
#include <string>
#include <climits>
using namespace std;
class Solution {
public:
int minCut(string s) {
int n = s.size();
vector<vector<bool>> palindrome(n, vector<bool>(n, false));
for (int i = n - 1; i >= 0; --i) {
for (int j = i; j < n; ++j) {
if (s[i] == s[j] && (j - i < 2 || palindrome[i + 1][j - 1])) {
palindrome[i][j] = true;
}
}
}
vector<int> memo(n, -2);
return dfs(s, 0, palindrome, memo);
}
private:
int dfs(const string& s, int start, const vector<vector<bool>>& palindrome, vector<int>& memo) {
if (start == s.size()) return -1;
if (memo[start] != -2) return memo[start];
int minCuts = INT_MAX;
for (int end = start; end < s.size(); ++end) {
if (palindrome[start][end]) {
int cuts = 1 + dfs(s, end + 1, palindrome, memo);
if (cuts < minCuts) minCuts = cuts;
}
}
memo[start] = minCuts;
return minCuts;
}
};
// int main() {
// Solution sol;
// cout << sol.minCut("aab") << endl; // 1
// return 0;
// }
Line Notes
vector<vector<bool>> palindrome(n, vector<bool>(n, false));2D vector to store palindrome info
for (int i = n - 1; i >= 0; --i) {Fill palindrome table bottom-up
if (s[i] == s[j] && (j - i < 2 || palindrome[i + 1][j - 1])) {Palindrome check using smaller substring
vector<int> memo(n, -2);Memo vector initialized with sentinel to indicate uncomputed
if (memo[start] != -2) return memo[start];Return cached result if available
for (int end = start; end < s.size(); ++end) {Try all palindrome substrings starting at start
memo[start] = minCuts;Cache computed minimum cuts
var minCut = function(s) {
const n = s.length;
const palindrome = Array.from({length: n}, () => Array(n).fill(false));
for (let i = n - 1; i >= 0; i--) {
for (let j = i; j < n; j++) {
if (s[i] === s[j] && (j - i < 2 || palindrome[i + 1][j - 1])) {
palindrome[i][j] = true;
}
}
}
const memo = new Array(n).fill(undefined);
function dfs(start) {
if (start === n) return -1;
if (memo[start] !== undefined) return memo[start];
let minCuts = Infinity;
for (let end = start; end < n; end++) {
if (palindrome[start][end]) {
let cuts = 1 + dfs(end + 1);
if (cuts < minCuts) minCuts = cuts;
}
}
memo[start] = minCuts;
return minCuts;
}
return dfs(0);
};
// console.log(minCut("aab")); // 1
Line Notes
const palindrome = Array.from({length: n}, () => Array(n).fill(false));2D array for palindrome info
for (let i = n - 1; i >= 0; i--) {Fill palindrome table bottom-up
if (s[i] === s[j] && (j - i < 2 || palindrome[i + 1][j - 1])) {Palindrome check using smaller substring
const memo = new Array(n).fill(undefined);Memo array to cache results
if (memo[start] !== undefined) return memo[start];Return cached result if available
for (let end = start; end < n; end++) {Try all palindrome substrings starting at start
memo[start] = minCuts;Cache computed minimum cuts
TimeO(n^2) for palindrome table + O(n^2) for recursion with memo
SpaceO(n^2) for palindrome table + O(n) for memo and recursion stack
Precomputing palindrome substrings takes O(n^2), and each index is computed once in recursion with O(n) substring tries.
💡 For n=1000, this means about 1 million operations, which is feasible.
Interview Verdict: Accepted
This approach is efficient enough for typical interview constraints and demonstrates DP mastery.