动态规划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]的含义—>递推公式—>初始化—>遍历顺序,
尤其要注意,初始化和遍历都要在递推公式确定之后才能真正确定!
另外,本题在实际上是线性的双指针的操作,而在动规的逻辑上用二维数组存储了,所以第一次做会比较抽象
动态规划最强总结篇
(这个图是代码随想录知识星球成员:青 所画)