647. 回文子串
文档讲解 : 代码随想录 - 647. 回文子串
状态:再次回顾。
动态规划五部曲:
-
确定dp数组(dp table)以及下标的含义
dp[i][j]
:表示区间范围[i,j]
(注意是左闭右闭)的子串是否是回文子串,如果是dp[i][j]
为true
,否则为false
。 -
确定递推公式
- 当
s[i]
与s[j]
不相等,dp[i][j]
一定是false
。 - 当
s[i]
与s[j]
相等时,有如下三种情况:- 情况一:下标
i
与j
相同,同一个字符例如a,当然是回文子串 - 情况二:下标
i
与j
相差为1,例如aa,也是回文子串 - 情况三:下标
i
与j
相差大于1的时候,例如cabac,此时s[i]
与s[j]
已经相同了,我们看i
到j
区间是不是回文子串就看aba是不是回文就可以了,那么aba的区间就是i+1
与j-1
区间,这个区间是不是回文就看dp[i + 1][j - 1]
是否为true
。
- 情况一:下标
if (s[i] == s[j]) { if (j - i <= 1) { // 情况一 和 情况二 result++; dp[i][j] = true; } else if (dp[i + 1][j - 1]) { // 情况三 result++; dp[i][j] = true; } }
- 当
-
dp数组初始化
dp[i][j]
初始化为false
-
确定遍历顺序
从递推公式中可以看出,情况三是根据dp[i + 1][j - 1]
是否为true
,在对dp[i][j]
进行赋值true
的。dp[i + 1][j - 1]
在dp[i][j]
的左下角,如图:
所以一定要从下到上,从左到右遍历,这样保证dp[i + 1][j - 1]
都是经过计算的。 -
举例推导dp数组:
举例,输入:"aaa"
,dp[i][j]
状态如下:
本体代码:
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;
} else if (dp[i + 1][j - 1]) { // 情况三
result++;
dp[i][j] = true;
}
}
}
}
return result;
}
};
- 时间复杂度: O ( n 2 ) O(n^2) O(n2)
- 空间复杂度: O ( n 2 ) O(n^2) O(n2)
516.最长回文子序列
文档讲解 : 代码随想录 - 516.最长回文子序列
状态:再次回顾。
回文子串是要连续的,回文子序列可不是连续的!
动态规划五部曲:
-
确定dp数组(dp table)以及下标的含义
dp[i][j]
:字符串s在[i, j]
范围内最长的回文子序列的长度为dp[i][j]
。 -
确定递推公式
if (s[i] == s[j]) { dp[i][j] = dp[i + 1][j - 1] + 2; } else { dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]); }
-
dp数组初始化
从递推公式:dp[i][j] = dp[i + 1][j - 1] + 2;
可以看出 递推公式是计算不到i
和j
相同时候的情况。
所以需要手动初始化一下,当i
与j
相同,那么dp[i][j]
一定是等于1
的,即:一个字符的回文子序列长度就是1
。其他情况dp[i][j]
初始为0
,这样递推公式:dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]);
中dp[i][j]
才不会被初始值覆盖。vector<vector<int>> dp(s.size(), vector<int>(s.size(), 0)); for (int i = 0; i < s.size(); i++) dp[i][i] = 1;
-
确定遍历顺序
从递归公式中,可以看出,dp[i][j]
依赖于dp[i + 1][j - 1]
,dp[i + 1][j]
和dp[i][j - 1]
,如图:
所以一定要从下到上,从左到右遍历,这样保证下一行的数据都是经过计算的。
- 举例推导dp数组:
输入s:"cbbd"
为例,dp数组状态如图:
本题代码:
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 + 1][j], dp[i][j - 1]);
}
}
}
return dp[0][s.size() - 1];
}
};
- 时间复杂度: O ( n 2 ) O(n^2) O(n2)
- 空间复杂度: O ( n 2 ) O(n^2) O(n2)
动态规划总结篇
文档讲解 : 代码随想录 - 动态规划总结篇
状态:再次回顾。
动规五部曲分别为:
- 确定dp数组(dp table)以及下标的含义
- 确定递推公式
- dp数组如何初始化
- 确定遍历顺序
- 举例推导dp数组