leetcode1278. 分割回文串III

给你一个由小写字母组成的字符串 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];

}

你可能感兴趣的:(DP)