LeetCode132. 分割回文串 II—字符串动态规划

题目概述

在这里插入图片描述
题目链接:点我做题

题解

一、普通动态规划

  还是那句话,字符串类的动态规划可以考虑往考虑前i个字符的反应问题的状态靠,比如本题,定义 f ( i ) f(i) f(i)为考虑字符串s的前i个字符分割为回文子串的最小分割数,假设字符串s的长度是n,那么问题的答案就是 f ( n ) f(n) f(n).
  考虑状态转移方程:如果此时从头到底i个字符已经是回文串了,那么不需要分割,分割数是0;否则可以这样把问题分解:把串从第j个字符分开( 0 < j < i 0<j<i 0<j<i),如果第j+1个字符到第i个字符构成了回文串,那么就得到了一种分割方案:把前j个字符分割为回文子串再加上第j+1个字符到第j个字符也形成了回文子串,这时分割数就等于前j个字符分割为回文子串的最小分割数 f ( j ) f(j) f(j)再加上第j个字符后的那一刀分割,即 f ( j ) + 1 f(j)+1 f(j)+1,为了得到最小分割数,我们需要遍历j从1到i-1,选出符合条件的最小分割数。
  至于如何判断回文串,可以用双指针法。
  本题的边界条件,显然 f ( 0 ) = 0 , f ( 1 ) = 0 f(0) = 0,f(1) = 0 f(0)=0,f(1)=0,0个字符不需要分割、1个字符也不用分割它自己就是回文串;其他的情况,由于我们要求的是最小值,一种方案是全都初始化为INT_MAX,另一种方案是发现含有i个字符的区间,最糟情况是分割i-1下,使得分成i个单个字符,它们都是回文串了,因此初始条件也可以写成 f ( i ) = i − 1 f(i)=i - 1 f(i)=i1.
代码:

class Solution {
public:
    int minCut(string s)
    {
        /*
        f(i)考虑第1个字符到第i个字符,分割为回文子串的最小分割数
        状态转移方程:
        如果整体是一个回文串 也就是j等于0时 第j+1=1个字符到第i个字符是回文串
        则f(i)等于0
        否则j从1开始,如果第j+1个字符到第i个字符为回文串
        则方法数等于在第j个字符后且一刀 1加上前面到底j个字符的方法数f(j)
        也就是 f(i) = f(j) + 1 j < i
        所以做一个搜索 f(i) = min(check(f(j))) + 1
        */
        int size = s.size();
        if (check(s, 0, size - 1) == true)
        {
            return 0;
        }
        vector<int> dp(size + 1, INT_MAX);
        /*
         *这里初始状态也可以弄成dp[0] = 0 其他的i dp[i] = i - 1
         *初始最多分割次数就是都分割成1个字符
        */
        dp[0] = 0;
        dp[1] = 0;
        for (int i = 2; i <= size; ++i)
        {
            for (int j = 0; j < i; ++j)
            {
                if (check(s, j, i - 1) == true)
                {
                    if (j == 0)
                    {
                        /*对应整体*/
                        dp[i] = 0;
                        break;
                    }
                    else
                    {
                        /*对应j=1开始到i-1*/
                        dp[i] = min(dp[i], dp[j] + 1);
                    }
                }
            }
        }
        return dp[size];
    }
    bool check(const string& s, int left, int right)
    {
        if (left == right)
        {
            return true;
        }
        while (left < right)
        {
            if (s[left] != s[right])
            {
                return false;
            }
            ++left;
            --right;
        }
        return true;
    }
};

时间复杂度: O ( n 3 ) O(n^3) O(n3)
空间复杂度: O ( n ) O(n) O(n)

二、记忆化搜索优化

  本题还可以再优化,判断子串是否为回文串这一操作我们可以提前做好,并把状态储存在一个二维矩阵 s t a t e state state中, s t a t e [ i ] [ j ] state[i][j] state[i][j]表示下标为i到下标为j的子串是否为回文串,关于判断是否为回文串,我们之前在最长回文子串的三种解法中介绍过一种动态规划的方法,即定义F(i,j)表示下标为i到下标为j的子串是否为回文子串,状态转移方程为 F ( i , j ) = ( s [ i ] = = s [ j ] ) & & F ( i + 1 , j − 1 ) F(i,j) = (s[i]==s[j])\&\&F(i+1,j-1) F(i,j)=(s[i]==s[j])&&F(i+1,j1),初始状态为 F ( i , i ) = t r u e , F ( i , i + 1 ) = ( s [ i ] = = s [ i + 1 ] ) F(i,i)=true,F(i,i+1) = (s[i]==s[i+1]) F(i,i)=true,F(i,i+1)=(s[i]==s[i+1]),这里可以单独拿出来作为一个 O ( n 2 ) O(n^2) O(n2)的算法放在外面把每个子串是否为回文子串先判断好咯,然后进循环直接判断 [ j , i − 1 ] [j,i-1] [j,i1]是否为回文串是直接看 s t a t e [ j ] [ i − 1 ] state[j][i-1] state[j][i1]即可。
  针对这个状态转移方程,本人了解的有两种写法,一种是让外层的循环i从大到小遍历,内层的循环j从i到n遍历,这也就是从对角线右下角往上遍历的思路;另一种写法是控制此时判断的回文子串长度,从L=2开始遍历到L=n,因为判断长度为L的串是否为回文串需要的关系是长度为L-2的串是否为回文串,所以这样遍历也保证了可以覆盖答案。
  两种实现的代码都给出:
从对角线右下角往上遍历:

class Solution {
public:
    int minCut(string s)
    {
        /*
        刚刚的算法是一个O(N^3)的算法,其实可以提前把判断(j,i-1)是否是回文串给分割出来
        放到外面写 至于判断是否是回文串 可以用动态规划
        f(i,j) = (s[i] == s[j]) && f(i + 1, j - 1)
        f(i,i) = true; f(i, i + 1) = (s[i] == s[i + 1])
        */
        int size = s.size();
        auto state = getstate(s);
        /*
        走原本的动态规划 F(i) = 0 if state(0, i - 1) == true
        else F(i) = min(F(i), F(j) + 1) j满足 state(j,i - 1) == true
        */
        vector<int> dp(size + 1);
        for (int i = 1; i <= size; ++i)
        {
            /*最初的最少分割次数就是字符数减1 即全都分割为1个字符*/
            dp[i] = i - 1;
        }
        for (int i = 2; i <= size; ++i)
        {
            for (int j = 0; j < i; ++j)
            {
                if (state[j][i - 1])
                {
                    if (j == 0)
                    {
                        dp[i] = 0;
                        break;
                    }
                    else
                    {
                        dp[i] = min(dp[i], dp[j] + 1);
                    }
                }
            }
        }
        return dp[size];
    }
    vector<vector<bool>> getstate(const string& s)
    {
        int n = s.size();
        vector<vector<bool>> ret(n, vector<bool>(n));
        for (int i = n - 1; i >= 0; --i)
        {
            for (int j = i; j < n; ++j)
            {
                if (i == j)
                {
                    ret[i][j] = true;
                }
                else if (j == i + 1)
                {
                    ret[i][j] = (s[i] == s[j]);
                }
                else
                {
                    ret[i][j] = (s[i] == s[j]) && ret[i + 1][j - 1];
                }
            }
        }
        return ret;
    }
};

控制每轮循环判断的回文串长度:

class Solution {
public:
    int minCut(string s)
    {
        /*
        刚刚的算法是一个O(N^3)的算法,其实可以提前把判断(j,i-1)是否是回文串给分割出来
        放到外面写 至于判断是否是回文串 可以用动态规划
        f(i,j) = (s[i] == s[j]) && f(i + 1, j - 1)
        f(i,i) = true; f(i, i + 1) = (s[i] == s[i + 1])
        */
        int size = s.size();
        auto state = getstate(s);
        /*
        走原本的动态规划 F(i) = 0 if state(0, i - 1) == true
        else F(i) = min(F(i), F(j) + 1) j满足 state(j,i - 1) == true
        */
        vector<int> dp(size + 1);
        for (int i = 1; i <= size; ++i)
        {
            /*最初的最少分割次数就是字符数减1 即全都分割为1个字符*/
            dp[i] = i - 1;
        }
        for (int i = 2; i <= size; ++i)
        {
            for (int j = 0; j < i; ++j)
            {
                if (state[j][i - 1])
                {
                    if (j == 0)
                    {
                        dp[i] = 0;
                        break;
                    }
                    else
                    {
                        dp[i] = min(dp[i], dp[j] + 1);
                    }
                }
            }
        }
        return dp[size];
    }
    vector<vector<bool>> getstate(const string& s)
    {
        int n = s.size();
        vector<vector<bool>> ret(n, vector<bool>(n));
        for (int i = 0; i < n; ++i)
        {
            ret[i][i] = true;
        }
        for (int L = 2; L <= n; ++L)
        {
            for (int i = 0; i < n; ++i)
            {
                int j = i + L - 1;
                if (j >= n)
                {
                    break;
                }
                if (L == 2)
                {
                    ret[i][j] = (s[i] == s[j]);
                }
                else
                {
                    ret[i][j] = (s[i] == s[j]) && ret[i + 1][j - 1];
                }
            }
        }
        return ret;
    }
};
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
动态规划方法可以用来解决分割回文串的问题。可以根据给定的字符串s,使用动态规划找到所有可能的回文子串。 首先,我们可以定义一个二维数组dp,其中dp[i][j]表示字符串s从索引i到j的子串是否是回文串。对于任意的i和j,如果s的第i个字符和第j个字符相等,并且s的第i+1个字符到第j-1个字符是回文串,则dp[i][j]为true。 然后,我们可以使用动态规划填充dp数组。我们可以从字符串s的末尾开始遍历,每次遍历一个字符。对于每个索引i,我们再从i开始向右遍历,直到字符串的末尾。对于每个索引i和j,我们检查字符串s从索引i到j是否是回文串。如果是回文串,则将这个子串添加到结果集中,并继续向右遍历,搜索下一个可能的回文子串。 最后,当我们遍历完整个字符串s时,我们就可以得到所有可能的分割方案。每个分割方案都是由一组回文子串组成的。 下面是一个使用动态规划的实现示例代码: ```cpp class Solution { public: vector<vector<string>> partition(string s) { vector<vector<string>> result; vector<string> path; vector<vector<bool>> dp(s.size(), vector<bool>(s.size(), false)); backtracking(s, 0, path, result, dp); return result; } void backtracking(string const& s, int startIndex, vector<string>& path, vector<vector<string>>& result, vector<vector<bool>>& dp) { if (startIndex >= s.size()) { result.push_back(path); return; } for (int i = startIndex; i < s.size(); i++) { if (s[startIndex == s[i && (i - startIndex <= 2 || dp[startIndex + 1][i - 1])) { dp[startIndex][i = true; path.push_back(s.substr(startIndex, i - startIndex + 1)); backtracking(s, i + 1, path, result, dp); path.pop_back(); } } } }; ``` 这是一个基于回溯和动态规划算法,它可以找到字符串s所有可能的分割方案,使得每个子串都是回文串。你可以根据自己的需求使用这个算法来解决分割回文串的问题。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [131. 分割回文串 回溯 c++](https://blog.csdn.net/qq_39993896/article/details/127132759)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [LeetCode-分割回文串C++)](https://blog.csdn.net/weixin_42817333/article/details/125468202)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值