s
,和一个整数 k
。s
中的部分字符修改为其他的小写英文字母。s
分割成 k
个非空且不相交的子串,并且每个子串都是回文串。示例 1:
输入:s = "abc", k = 2
输出:1
解释:你可以把字符串分割成 "ab" 和 "c",并修改 "ab" 中的 1 个字符,将它变成回文串。
示例 2:
输入:s = "xyabapp", k = 3
输出:1
解释:你可以把字符串分割成 "xy"、"aba" 和 "pp",只有xy不是回文串。
示例 3:
输入:s = "leetcode", k = 8
输出:0
提示:
1 <= k <= s.length <= 100
s
中只含有小写英文字母。
/* 首先,这种题就是DP 除了dp其他都会超时,想不出dp就别浪费时间了。。。
举个例子:"xyaba" n = 5, k = 2 (优解是1 "xy","aba")
看n=5时的状态,它虽然不是从n=4的状态转来的(这并不意味着这不是dp)
但它是从n=2的状态转化来的(n=2:"xy" ,"aba"),因此这不是一层紧挨一层的dp
而是一种分割关系的dp,也就是说当前状态n的dp 不一定用的就是n-1状态的dp
但用的一定是前面状态的dp,而前面状态dp一定是
2,以dp[len][k]表示:前len个[0,len-1]的串中,分割成k个回文串的最小代价
则:dp[len][k] 由 dp[subLen][k-1] + func(subLen , len-1)
长度为len的串分成k个,是由len中某个分割点为界,该分割点前面分割成k-1个(用到了之前dp)
再以该分割点到当前len结尾(计算一个子串的代价,函数实现)
3,我们求最小代价,一开始初始化全置为最大值 但不要用INT_MAX,我们涉及dp的相加
题最长才100,代价最多理论才50, 放个1024绝对够了
4, len从小1到大n-1 直至到整个串的长度,我们对于子串len
需要求出它分成[1,k]个的代价,所以过程中有很多很多不必要的步骤
但只要逻辑正确,时间空间没问题的 毕竟是dp 本身就很优化了
*/
int gett(string& s, int beg, int ed) {
int cont = 0; //得到子串[beg, ed] 变成回文的代价
while (beg < ed) {
if (s[beg++] != s[ed--])
++cont;
}
return cont;
}
int palindromePartition(string s, int k) {
if (k == 1)
return gett(s, 0, s.size() - 1);
if (k == s.size())
return 0; //注意这里是0,每个字符不用代价
int n = s.size(); // dp[n][k] 字符串的前n个字符,分割成k段的最小代价
vector< vector<int> > dp(n + 1, vector<int>(k + 1, 1024));
dp[0][0] = 0; //其他都是最大,但dp[0][0] = 0 由下面的代码推导出
for (int len = 1; len <= n; ++len) { /*当前子串长度,我们需要求出len从0开始
下标为[0,len-1]的子串,分割成12...的最优解,要以长度为第一层循环
因为在求解len长度的dp时,它肯定会用到小于len的dp结果,必须保证对于当前len,
小于len的dp是有结果的,那么最终结果就是dp[n][k] */
for (int cont = 1; cont <= k && cont <= len; ++cont) {
/* 对于len长度>=1的子串,最少分割成1个(一次调用函数) 最大分割成len个(每个字符一组)*/
for (int sub = cont - 1; sub < len; ++sub) {
/* 这是重点,对于长度为len (>=cont) 要分割成cont个的串,
此时已经求出长度为len-1的串(在分割成[1,k] 注意不是len,是k)下的最优解了
关键是从哪里开始分割 结束 边界情况,用sub表示前段串的长度
那么sub最少也得能分割成cont-1个(即长度最少为cont-1),
后半段(我们一次操作调用函数的区间):[sub ,len -1] 最小是1长度,一个字符
也就是sub: [cont - 1, len - 1], 对于当前长为len的串
它的前段串长度最少是[cont-1](保证能分割cont-1个),
最大是[len-1],因为dp[len]正在求 还没求出,况且[len-1,len-1]的后段正好一个了 */
dp[len][cont] = min(dp[len][cont], dp[sub][cont - 1] + gett(s, sub, len - 1));
/* 当前长为len要分割成cont个的dp[len][cont]结果是:
前段在分割成cont-1下的结果(以求出dp) + 后段分割成一个(调用函数即可)
看边界:len =1, cont = 1,sub = 0: dp[1][1] = dp[0][0] + get(0,0)<1> = 1 得知dp[0][0] =0
len = 2, cont = 1,sub =0 dp[2][1] = dp[0][0] + get(0,1)
len = 2,cont = 1,sub = 1 dp[2][1] = dp[1][0] + get(1,1) 此时出现了dp[1][0]
任何>=1长度都不可能分割成0个,不是我们算法错误 但无影响 dp[>=1][0] = 最大值
len = 2, cont = 2 ,sub = 1 : dp[2][2] = dp[1][1] + get(1,1)
*/
}
}
}
return dp[n][k];
}