题目
给你一个字符串 s ,找出其中最长的回文子序列,并返回该序列的长度。
子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。
示例 1:
输入:s = “bbbab”
输出:4
解释:一个可能的最长回文子序列为 “bbbb” 。
示例 2:
输入:s = “cbbd”
输出:2
解释:一个可能的最长回文子序列为 “bb” 。
代码
动态规划算法。初始化n ⋅ n n\cdot nn⋅n大小的d p dpdp数组,d p [ i ] [ j ] dp[i][j]dp[i][j]表示字符串i ii到j jj内的最长回文字序列的长度。转移方程为:
d p [ i ] [ j ] = { d p [ i + 1 ] [ j − 1 ] + 2 , s t r [ i ] = = s t r [ j ] m a x ( d p [ i + 1 ] [ j ] , d p [ i ] [ j − 1 ] ) , o t h e r w i s e dp[i][j]= \left\{\begin{matrix} & dp[i+1][j-1]+2 & ,str[i]==str[j]\\ & max(dp[i+1][j], dp[i][j-1]) & ,otherwise \end{matrix}\right.dp[i][j]={dp[i+1][j−1]+2max(dp[i+1][j],dp[i][j−1]),str[i]==str[j],otherwise
再注意当i = = j i==ji==j和i + 1 = = j i+1==ji+1==j的情形即可。
自己写的屑代码:
class Solution {
public:
int longestPalindromeSubseq(string s) {
int n = s.size();
vector<vector<int>> arr(n, vector<int>(n));
for(int i=0;i<n;i++) arr[i][i] = 1;
for(int i=0;i<n-1;i++) arr[i][i+1] = s[i] == s[i+1] ? 2 : 1;
for(int k=1;k<n;k++){
for(int i=0;i<n-k;i++){
arr[i][i+k] = max(arr[i][i+k-1], arr[i+1][i+k]);
if(s[i] == s[i+k]) arr[i][i+k] = max(arr[i][i+k], arr[i+1][i+k-1] + 2);
}
}
return arr[0][n-1];
}
};
为什么想写一下呢,主要是最近正活儿干的太慢。。。啊不,是在上面的代码中想着循环的层次顺序的问题,遍历的要点我认为是保证其依赖的子区间全部都计算过了,最简单的思路就是如以上代码,先遍历区间的长度k ( k = j − i ) k(k=j-i)k(k=j−i),再遍历区间的起始点i ii,这样最易于理解,而遍历i ii、j jj在脑子不清楚(比如写代码的时候)就会自我怀疑,它这个子区间都处理了吗?(类似于“我今天出门锁门了吗?”)。下面用excel简单画了个图看一下——我也是闲的。。。

要点说过了,在于保证当前区间的真子区间(就是不包括自己,我随便编的概念)都已经处理过了,我的代码为左图,下面的官方代码为中间图。
官方代码:
class Solution {
public:
int longestPalindromeSubseq(string s) {
int n = s.length();
vector<vector<int>> dp(n, vector<int>(n));
for (int i = n - 1; i >= 0; i--) {
dp[i][i] = 1;
char c1 = s[i];
for (int j = i + 1; j < n; j++) {
char c2 = s[j];
if (c1 == c2) {
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][n - 1];
}
};