✅做题思路or感想
子数组是连续的,子序列是不连续的
这一题动态规划的难点在于推导递推公式,以及许久未见过的遍历顺序
dp
数组含义
dp[i][j]
表示字符串s
在[i, j]
上的最长回文子序列
💡递推公式
字符串问题都要从最后一步的角度去分析。比如字符串有8个字符,不能从第一个字符到第二个字符这样分析,要从8个字符的情况分析,然后再到7个,6个这样由大向小分析
-
若
s[i] = s[j]
,说明主串的[i, j]
部分的外围符合了回文子序列的要求,可以向外拓展两个位置,故有之前的子序列最大长度拓展两个长度dp[i + 1][j - 1] + 2
-
若
s[i] != s[j]
,说明[i, j]
外围不符合要求,则说明s[i]
和s[j]
不能同时用了-
不匹配
s[i]
了,但是s[j]
可能还有匹配的余地,故保留s[j]
继续匹配,缩短匹配范围成[i - 1, j]
(注意这里是i + 1
,因为整个范围往里面坍缩!!!),则有dp[i][j] = dp[i + 1][j]
-
不匹配
s[j]
了,但是s[i]
可能还有匹配的余地,故保留s[i]
继续匹配,缩短匹配范围成[i, j - 1]
(这里的j - 1
同理是往范围里面坍缩的),则有dp[i][j] = dp[i][j - 1]
-
综上,
dp[i][j] = max(dp[i + 1][j], dp[i][j - 1])
-
💡初始化
这一题需要注意到单个字符的情况,单个字符一定是回文的,所以要初始化i = j
的情况皆是dp[i][j] = 1
💡💡💡遍历顺序
主要困难点!因为判断条件里有个dp[i + 1][j - 1]
,即是如果要判断dp[i][j]
的情况,则首先要了解dp[i + 1][j - 1]
的情况!
1 | 2 | |
---|---|---|
1 | dp[i + 1][j - 1] | |
2 | dp[i][j] |
由上图知,如果要让dp[i + 1][j - 1]
比dp[i][j]
要更早算出来,则遍历方向应该是从右往左,从上往下
- 对
i
是从大向小遍历 - 对
j
是从小向大遍历
这里还有最后一个要注意的地方:因为递推方向对i
是从大向小遍历,对j
是从小向大遍历,所以整个dp
数组的值是往这两个方向递增的!所以题目求的最大值应该是i
最小,j
最大的时候!!!这个点因为很少有题目考到所以尤为关键!!!!
class Solution {
public:
int longestPalindromeSubseq(string s) {
vector<vector<int>>dp (s.size() + 1, vector<int>(s.size() + 1, 0));
//初始化
for (int i = 0; i < s.size(); ++i) {
dp[i][i] = 1;
}
//注意这里的遍历顺序!这里遍历是为了截出[i, j]范围!!!
for (int i = s.size() - 1; i > -1; --i) {//写这种从大到小的循环时,很容易写出++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];//这里要特别注意!!!
}
};