题目描述
解题思路
如何判断一个字符串是否为回文字符串?每一次从两端各取一个字符判断这两个字符是否相等,就这样从两端向中间,如果每一次取出的两个字符都相等,那就是回文字符串。
上面的判断过程中“每次需要考虑字符串两端的字符”,自然而然地想到应该用二维DP,定义dp[i][j]
的含义为下标从i到j的字符str[i...j]
的最长回文子序列的长度。
初始化
当字符串的长度为1时,它的最长回文子序列就是它本身,长度为1:
// init(l=1)
for(int i=0; i<n; i++){
dp[i][i] = 1;
}
状态转移规律
当字符串str[i...j]
的长度大于1时:
- 如果
str[i] == str[j]
:那么str[i]
和str[j]
必然是str[i...j]
的最长回文子序列的一部分,实际上应该是str[i...j]
的最长回文子序列的两头的字符。如果将这两个字符先取出来,剩下字符串str[i+1...j-1]
,只要找到剩下的这个字符串的最长回文子序列,然后再加上两头的两个字符str[i]
和str[j]
就可以得到str[i...j]
的最长回文子序列,即:dp[i][j] = dp[i+1][j-1] + 2
。 - 如果
str[i] != str[j]
:那么str[i]
和str[j]
肯定不可能同时出现在str[i...j]
的最长回文子序列中,因为如果str[i]
和str[j]
同时出现,就一定是str[i...j]
的最长回文子序列的两头元素,而它们俩又不相等,这是矛盾的。因此,要么删掉str[i]
,要么删掉str[j]
,「求str[i...j]
的最长回文子序列的问题」就转换为「求str[i+1...j]
的最长回文子序列的问题」或者「求str[i...j-1]
的最长回文子序列的问题」,从两者中选更长的一个就行了。
d p [ i ] [ j ] = m a x { d p [ i + 1 ] [ j ] , d p [ i ] [ j − 1 ] } dp[i][j] = max\{dp[i+1][j], dp[i][j-1]\} dp[i][j]=max{dp[i+1][j],dp[i][j−1]}
// bottom-up calc
for(int l=2; l<=n; l++){
for(int i=0; i<=n-l; i++){
int j = i+l-1;
if(str[i] == str[j]){
dp[i][j] = (i+1<=j-1?dp[i+1][j-1]:0) + 2;
}else{
dp[i][j] = max(dp[i+1][j], dp[i][j-1]);
}
}
}
return dp[0][n-1];
C++代码实现
#include <iostream>
using namespace std;
string str;
int n;
void input(){
cin >> str;
n = str.length();
}
int dp[1000][1000];
int solve(){
// init(l=1)
for(int i=0; i<n; i++){
dp[i][i] = 1;
}
// bottom-up calc
for(int l=2; l<=n; l++){
for(int i=0; i<=n-l; i++){
int j = i+l-1;
if(str[i] == str[j]){
dp[i][j] = (i+1<=j-1?dp[i+1][j-1]:0) + 2;
}else{
dp[i][j] = max(dp[i+1][j], dp[i][j-1]);
}
}
}
return dp[0][n-1];
}
int main() {
input();
cout << solve();
}
// 64 位输出请用 printf("%lld")