动态规划解决回文串问题

有关回文串的问题可以用很多种算法来解决,比如:专门为解决回文串而设计的算法Manacher 算法 ,马拉车算法等等,可以看这里 。而本文只关心用动态规划的方法来解决。

文章涉及到 求
最长回文子串 leetcode 5
最长回文子序列 leetcode 516
最少分割次数使得都为回文子串 leetcode 132
最少插入次数使得为回文子串 leetcode 1312

对待回文串问题的动态规划状态定义,一般有两个

  • 一维数组 dp[i] 表示s[0…i]要形成回文子串的最少分割次数(这个定义还是比较少用的)
  • 二维数组 dp[i] [j] 表示 s[i…j]之间是否是回文串/最长回文子序列的长度 等等

对于状态的定义要和题目所求的量搭上关系

状态转移方程思考也有两个途径

  • 最常用的一种就是中心扩散的方法,即知道dp[i+1][j-1]的结果来求dp[i][j] (一般都是从左往右 从下往上推)
    在这里插入图片描述
  • 第二种就是顺序的求解 dp[i] 与 dp[i-1]搭上关系进行求解

1.最长回文子串 leetcode 5
在这里插入图片描述
状态定义
dp[i][j] 表示字符串 s 的第 i 到 j 个字母组成的串 s[i…j]是否为回文串
状态转移方程
dp[i][j] = dp[i+1][j-1] && (s[i]==s[j])
Base Case
i == j 时 dp[i][j] = 1

class Solution {

public:
    string longestPalindrome(string s) {
        int n = s.size();
        string ans = "";
        vector< vector<int> > dp( n, vector<int>(n,0) );

        for( int l = 0; l < n; l ++ ){
            for( int i = 0; i + l < n; i ++ ){
                int j = i + l;
                if( l == 0 ) dp[i][j] = 1;
                else if( l == 1 ) dp[i][j] = (s[i] == s[j]);
                else
                    dp[i][j] = dp[i+1][j-1] && (s[i] == s[j]);

                if( dp[i][j] == 1 && (j-i+1) > ans.size() )
                    ans = s.substr( i, (j-i+1) );
            }
        }
        return ans;
    }
};

2.最长回文子序列 leetcode 516
在这里插入图片描述
状态定义
dp[i][j]表示在子串 s[i…j] 中,最长回文子序列的长度为 dp[i][j]
状态转移方程

if (s[i] == s[j])
    dp[i][j] = dp[i + 1][j - 1] + 2;   // s[i] s[j]都在最长回文子序列中
else
    dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]); // s[i+1..j] 和 s[i..j-1] 谁的回文子序列更长?

Base Case
i == j 时 dp[i][j] = 1

class Solution {
public:
    int longestPalindromeSubseq(string s) {

        int n = s.size();
        vector< vector<int> > dp( n, vector<int>(n, 0) );

        for( int i = 0; i < n ; i ++ ){
            dp[i][i] = 1;
        }
        for( int i = n-1; i >= 0; i -- ){
            for( int j = i + 1; j < n ; j ++ ){
                if( s[i] == s[j] )
                    dp[i][j] = dp[i+1][j-1] + 2;
                else
                    dp[i][j] = max( dp[i][j-1], dp[i+1][j] );
            }
        }

        return dp[0][n-1];
    }
};

3.最少分割次数使得都为回文子串 leetcode 132
这道题的上一题leetcode131 求所有的分割方法,使用回溯法来解决。动态规划就是需要穷举出所有答案再求最值,回溯法只是穷举出答案,这道题要求最小分割次数,所以用动态规划来求解。
并且这里分割,很明显不能使用中心扩散的方法,所以只能使用一维dp
在这里插入图片描述
状态定义
dp[i]:表示前缀子串 s[0…i] 分割成若干个回文子串所需要最小分割次数。
状态转移方程

if  (s[0:i] 本身就是一个回文串) 
    那么不用分割 dp[i] = 0
else
	for j in (0..i)
	if (s[j + 1, i] 是回文])
	    dp[i] = min( dp[i], [dp[j] + 1 ) 

Base Case
单个字符一定是回文串 dp[0] = 0;

class Solution {

private:
    bool isPalindrome( const string &s, int l, int r ){

        while( l <= r ){
            if( s[l] != s[r] ) return false;
            l ++; r --;
        }

        return true;
    }

public:
    int minCut(string s) {

        int n = s.size();
        vector<int> dp(n,n);
        dp[0] = 0;

        for( int i = 1; i < n ; i ++ ){
            if( isPalindrome(s, 0, i) )
                dp[i] = 0;
            else{
                for( int j = 0; j+1 <= i; j ++ ){
                    if( isPalindrome( s, j+1, i ) )
                        dp[i] = min( dp[i], dp[j] + 1);
                }
            }

        }
        return dp[n-1];
    }
};

4.最少插入次数使得为回文子串 leetcode 1312
状态定义
dp[i][j] 表示s[i…j],最少需要进行dp[i][j]次插入才能变成回文串
状态转移方程

if(s[i] == s[j])
	dp[i][j] = dp[i+1][j-1];
else
	dp[i][j] = min( dp[i+1][j], dp[i][j-1] ) + 1;

Base Case
i == j时 dp[i][j] = 0

class Solution {
public:
    int minInsertions(string s) {

        int n = s.size();

        vector< vector<int> > dp( n, vector<int>(n, 0) );

        for( int i = n-1; i >= 0; i -- ){
            for( int j = i+1; j < n; j ++ ){
                if( s[i] == s[j] )
                    dp[i][j] = dp[i+1][j-1];
                else
                    dp[i][j] = min( dp[i][j-1], dp[i+1][j] ) + 1;
            }
        }

        return dp[0][n-1];
    }
};
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
动态规划分割回文串是一种常用的解决方案。在动态规划中,我们可以使用不同的状态定义和状态转移方程来解决这个问题。 一种常见的状态定义是使用一维数组dp[i],其中dp[i]表示字符串s的前i个字符形成回文子串的最少分割次数。这种定义可以通过判断s[j:i]是否为回文来进行状态转移,其中1 <= j <= i。具体的状态转移方程可以如下表示: - 当s[0:i]本身就是一个回文串时,不需要进行分割,即dp[i] = 0。 - 否则,我们可以遍历所有可能的分割点j,如果s[j+1:i]是回文串,那么我们可以将问题分割为两部分,即dp[i] = min(dp[i], dp[j] + 1)。 另一种状态定义是使用二维数组dp[i][j],其中dp[i][j]表示字符串s的前i个字符分割为j个子串的修改的最小字符数。在这种定义下,我们可以使用类似的状态转移方程来进行计算。具体的状态转移方程可以如下表示: - 当i < j时,不可能将前i个字符分割为j个子串,即dp[i][j] = INF。 - 当i >= j时,我们可以遍历所有可能的分割点k,计算dp[i][j]的最小值,即dp[i][j] = min(dp[i][j], dp[k][j-1] + cost(k+1, i)),其中cost(k+1, i)表示将子串s[k+1:i]修改为回文所需的最小字符数。 这两种定义和状态转移方程都可以用来解决动态规划分割回文串问题,具体使用哪种方法取决于具体的问题要求和效率要求。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [动态规划解决回文串问题](https://blog.csdn.net/qq_37414405/article/details/111317301)[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* [动态规划解分割回文串](https://blog.csdn.net/melody157398/article/details/119769501)[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
发出的红包

打赏作者

五月的天气

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值