class Solution:
def palindromePartition(self, s: str, k: int) -> int:
n = len(s)
# Precompute the cost to make s[i:j+1] a palindrome
cost = [[0]*n for _ in range(n)]
for l in range(2, n+1):
for i in range(n-l+1):
j = i + l - 1
cost[i][j] = cost[i+1][j-1] + (0 if s[i] == s[j] else 1)
# dp[i][kk]: min cost to split s[i:] into kk parts
from functools import lru_cache
@lru_cache(None)
def dp(i, kk):
if n - i == kk:
return 0 # Each char is a palindrome
if kk == 1:
return cost[i][n-1]
res = float('inf')
for j in range(i, n-kk+1):
res = min(res, cost[i][j] + dp(j+1, kk-1))
return res
return dp(0, k)
class Solution {
public:
int palindromePartition(string s, int k) {
int n = s.size();
vector<vector<int>> cost(n, vector<int>(n, 0));
// Precompute cost to make s[i..j] palindrome
for (int len = 2; len <= n; ++len) {
for (int i = 0; i + len - 1 < n; ++i) {
int j = i + len - 1;
cost[i][j] = cost[i+1][j-1] + (s[i] != s[j]);
}
}
// dp[i][kk]: min cost to split s[i:] into kk parts
vector<vector<int>> dp(n+1, vector<int>(k+1, INT_MAX/2));
for (int i = 0; i <= n; ++i) dp[i][0] = INT_MAX/2;
for (int i = 0; i <= n; ++i) dp[i][1] = (i < n) ? cost[i][n-1] : 0;
for (int kk = 2; kk <= k; ++kk) {
for (int i = 0; i <= n-kk; ++i) {
for (int j = i; j <= n-kk; ++j) {
dp[i][kk] = min(dp[i][kk], cost[i][j] + dp[j+1][kk-1]);
}
}
}
return dp[0][k];
}
};
class Solution {
public int palindromePartition(String s, int k) {
int n = s.length();
int[][] cost = new int[n][n];
// Precompute cost to make s[i..j] palindrome
for (int len = 2; len <= n; ++len) {
for (int i = 0; i + len - 1 < n; ++i) {
int j = i + len - 1;
cost[i][j] = cost[i+1][j-1] + (s.charAt(i) == s.charAt(j) ? 0 : 1);
}
}
int[][] dp = new int[n+1][k+1];
for (int i = 0; i <= n; ++i) {
for (int kk = 0; kk <= k; ++kk) {
dp[i][kk] = Integer.MAX_VALUE / 2;
}
}
for (int i = 0; i <= n; ++i) dp[i][1] = (i < n) ? cost[i][n-1] : 0;
for (int kk = 2; kk <= k; ++kk) {
for (int i = 0; i <= n-kk; ++i) {
for (int j = i; j <= n-kk; ++j) {
dp[i][kk] = Math.min(dp[i][kk], cost[i][j] + dp[j+1][kk-1]);
}
}
}
return dp[0][k];
}
}
var palindromePartition = function(s, k) {
const n = s.length;
// Precompute cost to make s[i..j] palindrome
const cost = Array.from({length: n}, () => Array(n).fill(0));
for (let len = 2; len <= n; ++len) {
for (let i = 0; i + len - 1 < n; ++i) {
let j = i + len - 1;
cost[i][j] = cost[i+1][j-1] + (s[i] === s[j] ? 0 : 1);
}
}
// dp[i][kk]: min cost to split s[i:] into kk parts
const dp = Array.from({length: n+1}, () => Array(k+1).fill(Infinity));
for (let i = 0; i <= n; ++i) dp[i][1] = (i < n) ? cost[i][n-1] : 0;
for (let kk = 2; kk <= k; ++kk) {
for (let i = 0; i <= n - kk; ++i) {
for (let j = i; j <= n - kk; ++j) {
dp[i][kk] = Math.min(dp[i][kk], cost[i][j] + dp[j+1][kk-1]);
}
}
}
return dp[0][k];
};
s
and an integer k
, split s
into k
non-empty, contiguous substrings. You must change the minimum number of characters in s
so that every substring is a palindrome. Return the minimum number of character changes needed.
s
into exactly k
parts.1 <= k <= s.length <= 100
s
consists of lowercase English letters.k
parts and, for each split, calculating how many character changes are needed for each part to become a palindrome. This brute-force approach would involve:
k
parts (which is exponential in nature), ands[i:j]
, calculate the minimum number of changes needed to make it a palindrome.cost[i][j]
.dp[i][kk]
represent the minimum changes needed to partition s[i:]
into kk
palindromic substrings.kk == 1
: dp[i][1] = cost[i][n-1]
(the whole remaining string is one part).j
(where the first part is s[i:j]
), and set:
dp[i][kk] = min(cost[i][j] + dp[j+1][kk-1])
for all valid j
.
dp[0][k]
.s = "abc"
, k = 2
cost[0][0]=0
, cost[0][1]=1
, cost[1][2]=1
, etc., and the recursive DP will select the minimum.
k
palindromic parts. This approach avoids redundant computation, leverages overlapping subproblems, and turns an exponential problem into a polynomial one. The elegance comes from precomputing costs and using DP to build the solution step by step.