力扣132.分割回文串II的两种解法

题目

给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是回文。
返回符合要求的 最少分割次数 。

示例 1: 输入:s = “aab” 输出:1 解释:只需一次分割就可将 s 分割成 [“aa”,“b”] 这样两个回文子串。

这题是hard,可以和131.分割回文串I,647.回文子串,5.最长回文子串三道题结合起来看,也相当于他们的升级版

解题基本原理——动规

对于下标从0~i的字符串,如果不是回文的话,那么假设中间1个分割点为下标j

dp[i]是0~i下标的字符串的分割次数,那么

  • 0 ~ i是回文时,显然dp[i] = 0;即不用分割
  • 0 ~ i不是回文时,如果分割点j使得 j ~ i是回文,那么dp[i] = dp[j] + 1;当然对于不同j,我们要取最小值,所以dp[i] = min(dp[i], dp[j] + 1);

注意:j一定要能取到i - 1,此时j + 1就是i,那么此时j + 1 = i, 而i ~ i 一定是回文

否则,像我一开始想的是,j - 1 < i,这样如果j + 1 ~ i是两个字符的话,那么这两个字符可能是回文,也可能不是回文。递推公式就更麻烦了。

解法一:双指针判断回文+动规

下面我用O(N)的双指针法写函数来判断子串是否回文,这样需要反反复复调用函数,使得耗时很大。

class Solution {
private:
    //判断是否回文串的函数
    bool isHui(const string& s, int start, int end){
        for(int i = start, j = end; i < j; i++, j--){
            if(s[i] != s[j]) return false;
        }
        return true;
    }

public:
    int minCut(string s) {
        vector<int>dp(s.size(), INT_MAX);
        dp[0] = 0;
        for(int i = 1; i < s.size(); i++){
            //递推公式是适用于0~i不是回文的,所以先处理0~i为回文情况
            if(isHui(s, 0, i)){
                dp[i] = 0;
                continue;//注意要跳过当前i了
            }
            for(int j = 0; j < i;j++){ // j 要取到i - 1,这样才能使得j + 1= i ~ i一定是回文,因为递推公式,比如dp[2] = min(dp[2], dp[1] + 1)
                if(isHui(s, j+1, i)){
                    dp[i] = min(dp[i], dp[j] + 1);
                }
            }
        }
        return dp[s.size() - 1];
    }
};

力扣132.分割回文串II的两种解法_第1张图片
== 在for循环里反复调用函数,使得耗时太大。不过内存消耗小。==

解法二:动规判断子串是否回文+动规确认分割次数

用dp数组,记录子串是否为回文串,这样避免函数的调用。
dp数组记录是否为回文的方法就是647题回文子串
这里使用精简过的,原理就是:
如果当前s[i] == s[j],那么如果i和j相差不大于1,也就是要么i和j指向同一个字符,要么i到j就是两个字符,比如aa,那此时肯定是回文。
或者就是递推关系,即i + 1到 j - 1也是回文

vector<vector<bool>> isPalindromic(s.size(), vector<bool>(s.size(), false));
for (int i = s.size() - 1; i >= 0; i--) {
    for (int j = i; j < s.size(); j++) {
        if (s[i] == s[j] && (j - i <= 1 || isPalindromic[i + 1][j - 1])) {
            isPalindromic[i][j] = true;
        }
    }
}

完整代码如下:

class Solution {
public:
    int minCut(string s) {
        vector<vector<bool>>isHui(s.size(), vector<bool>(s.size(), false));
        //dp[i][j]中,i从后往前遍历,j从前往后遍历
        for(int i = s.size() - 1; i >= 0; i--){
            for(int j = i; j < s.size(); j++){
                if(s[i] == s[j] && (j - i <= 1 || isHui[i + 1][j - 1] == true)){
                    isHui[i][j] = true;
                }
            }
        }
        vector<int>dp(s.size(), INT_MAX);
        dp[0] = 0;
        for(int i = 1; i < s.size(); i++){
            if(isHui[0][i]){ //用的dp数组查询当前子串是否为回文
                dp[i] = 0;
                continue;
            }
            for(int j = 0; j < i;j++){
                if(isHui[j + 1][i]){
                    dp[i] = min(dp[i], dp[j] + 1);
                }
            }
        }
        return dp[s.size() - 1];
    }
};

提交结果:
力扣132.分割回文串II的两种解法_第2张图片

执行用时优化了很多!

你可能感兴趣的:(算法学习,leetcode,算法,动态规划,c++)