动态规划day46:回文与子序列|647. 回文子串、516. 最长回文子序列、动态规划最强总结篇

动态规划day46:回文与子序列|647. 回文子串、516. 最长回文子序列、动态规划最强总结篇

647. 回文子串

给你一个字符串 s ,请你统计并返回这个字符串中 回文子串 的数目。

回文字符串 是正着读和倒过来读一样的字符串。

子字符串 是字符串中的由连续字符组成的一个序列。

示例 1:

输入:s = “abc”
输出:3
解释:三个回文子串: “a”, “b”, “c”

示例 2:

输入:s = “aaa”
输出:6
解释:6个回文子串: “a”, “a”, “a”, “aa”, “aa”, “aaa”

提示:

  • 1 <= s.length <= 1000
  • s 由小写英文字母组成
class Solution {
public:
    int countSubstrings(string s) {
        vector<vector<bool>> dp(s.size(),vector<bool>(s.size(),false));
        int result=0;
        for(int i=s.size()-1;i>=0;i--)
            for(int j=i;j<s.size();j++)
            {
                if(s[i]==s[j])
                {
                    if(j-i<=1)
                    {
                        result++;
                        dp[i][j]=true;
                    }
                    if(j-i>1&&dp[i+1][j-1]==true)
                    {
                        result++;
                        dp[i][j]=true;
                    }
                }
            }
            return result;
    }
};

难点:

  • dp数组的定义:如果我们将dp数组定义为字符串中回文子串的数目,递推关系找不到或者非常难找,这个时候我们就需要重新考虑dp数组的定义了。正确的dp[i] [j]的定义是:在**闭区间[i,j]**范围内的字符串是否为回文串。这样的定义就很容易找到递推关系。
  • 递推关系:很显然,s[i]==s[j]的时候,只要dp[i+1] [j-1]=true,那么dp[i] [j]=true。但是**不要漏掉另一种情况:**当j-i=0或j-i=1的时候是没有dp[i+1] [j-1]的,而且这种情况是很恒立的。
  • 在dp数组正确定义之后,最难的其实是遍历顺序记住:遍历顺序必须基于递推公式。由递推公式可知,dp[i] [j]是由dp[i+1] [j-1]推导出来的,而dp[i+1] [j-1]在dp[i] [j]的左下方。也就是说,只有先有左下方的数据,才能推导出后面的值。所以遍历顺序是:先左后右,先下后上。
  • 初始化:其实并不需要特殊考虑左下方的数值,直接全部赋为false即可,因为左下方的值是包含在递推公式中的。所以初始化也要在递推公式之后考虑!

516. 最长回文子序列

给你一个字符串 s ,找出其中最长的回文子序列,并返回该序列的长度。

子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。

示例 1:

输入:s = “bbbab”
输出:4
解释:一个可能的最长回文子序列为 “bbbb” 。

示例 2:

输入:s = “cbbd”
输出:2
解释:一个可能的最长回文子序列为 “bb” 。

提示:

  • 1 <= s.length <= 1000
  • s 仅由小写英文字母组成
class Solution {
public:
    int longestPalindromeSubseq(string s) {
        vector<vector<int>> dp(s.size(),vector<int>(s.size(),0));
        for(int i=0;i<s.size();i++)
        dp[i][i]=1;
        for(int i=s.size()-1;i>=0;i--)
            for(int j=i+1;j<s.size();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][s.size()-1];
    }
};

动规五部曲:

  • dp[i] [j]的含义:从 i 到 j 范围内的回文子序列的最大长度,所以最后返回的就是dp[0] [s.size()-1]

  • 递推公式:

    • 当s[i]=s[j],易得:dp[i] [j]=dp[i+1] [j-1]+2
    • 当s[i]!=s[j],因为首尾不一样,所以这个字符串要么等效于减去首元素后所剩的,要么等效于减去尾元素后所剩的,所以dp[i] [j]=max(dp[i] [j-1],dp[i+1] [j])
  • 初始化:通过列递推公式,我们发现,算式的根基就是当 i 和 j 指向同一个元素的时候,即dp[i] [i],很显然,dp[i] [i] =1,其他的归0即可,即:

for(int i=0;i<s.size();i++)
      dp[i][i]=1;
  • 遍历顺序:和647的分析方法类似,最后的结论也是从下到上,从左到右遍历
  • 检验

注意:动规问题的分析思路的流程且不可打乱:
dp[i] [j]的含义—>递推公式—>初始化—>遍历顺序
尤其要注意,初始化和遍历都要在递推公式确定之后才能真正确定!

另外,本题在实际上是线性的双指针的操作,而在动规的逻辑上用二维数组存储了,所以第一次做会比较抽象

动态规划最强总结篇

在这里插入图片描述
(这个图是代码随想录知识星球成员:青 所画)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值