NC154 最长回文子序列
题意
给定一个字符串,找到其中最长的回文子序列,并返回该序列的长度。字符串长度
1. 暴力做法(不合要求, tle)
递归计算区间[i, j]的最长回文子序列,设最长回文子序列为f(i, j), 分为三种情况:
- 如果
s[i] == s[j], 那么最长子序列可能是2+f(i-1, j-1) - 否则,i和j不能同时取到,最长子序列可能是
f(i-1, j)和f(i, j-1)的最大值。
分别递归计算上面三种情况的较大值即可。
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param s string 一个字符串由小写字母构成,长度小于5000
* @return int
*/
string s;
int longestPalindromeSubSeq(string s2) {
int n = s2.size();
s = s2;
return f(0, n-1);
}
int f(int i, int j) {
if (i == j) return 1; // 只有1个元素,答案是1
if (i > j) return 0; // 如果区间不合法,没有方案
int res = -1;
if (s[i] == s[j]) res = max(res, f(i+1, j-1)+2); // 头尾是相同元素,多一种情况
res = max(res, f(i+1, j));
res = max(res, f(i, j-1));
return res;
}
};- 时间复杂度:
, 每个字符都枚举了选择和不选择两种情况
- 空间复杂度:
, 每层递归都占用了
的空间。
2. 区间DP
事实上方法1如果加上了记忆化搜索,就得到了一种区间DP的思路, 设dp[i][j]表示区间[i, j]的最长回文子序列,则有以下关系:
- 如果
s[i] == s[j],dp[i][j] = max(dp[i][j], dp[i-1][j-1]+2) - 否则,i和j不能同时取到,
dp[i][j] = max(dp[i+1][j] ,dp[i][j-1]
最后dp[0][n-1]就是答案。
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param s string 一个字符串由小写字母构成,长度小于5000
* @return int
*/
static const int N = 5010; // 题目注释中说s的最大长度是5000,所以这里开5000*5000的数组
int dp[N][N];
int longestPalindromeSubSeq(string s) {
int n = s.size();
memset(dp, 0, sizeof(dp));
for (int i = 0; i < n; i++) dp[i][i] = 1;
// 重点注意,i跟i+1有关,j跟j-1有关,所以i要倒过来算
// 如果i是正向算的,那么dp[i+1]还没算出来,就会wa
for (int i = n-1; i >= 0; i--) {
for (int j = i+1; j < n; j++) {
dp[i][j] = max(dp[i+1][j], dp[i][j-1]);
if (s[i] == s[j])
dp[i][j] = max(dp[i+1][j-1]+2, dp[i][j]);
}
}
return dp[0][n-1];
}
};- 时间复杂度:
, 两重循环。
- 空间复杂度:
, 开了二维数组。
3. 转化为lcs模型,dp
我们发现回文串有一个重要的特点,正序和逆序是一样的,那么,如果我们把s倒过来得到串t,那么s的最长回文子序列不就是s和t的最长公共子序列吗?
我们用一个图来描述下:
那么黄色部分正着看和倒着看是一样的,因此可以使用lcs的模型来计算,接下来套用lcs问题的模板就可以了。
lcs问题的解法简单描述如下,设dp[i][j]表示s串和t串的前i个字符和前j的字符的子串的最长公共子序列。那么有如下的转移关系:
- 如果
s[i] == t[j], 则dp[i][j] = dp[i-1][j-1]+1 - 否则s[i]和t[j]一定有一个不在答案中,则
dp[i][j] = max(dp[i-1][j], dp[i][j-1]
转移过程如下图所示:
最后,本题中s和t的长度一样,则即为答案。
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param s string 一个字符串由小写字母构成,长度小于5000
* @return int
*/
static const int N = 5010;
int dp[N][N];
int longestPalindromeSubSeq(string s) {
int n = s.size();
string t = s;
// 求s的逆序串t
reverse(t.begin(), t.end());
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
dp[i][j] = max(dp[i][j-1], dp[i-1][j]);
if (s[i-1] == t[j-1]) // dp序列从1开始,而s和t下标从0开始,故减1修正
dp[i][j] = max(dp[i][j], dp[i-1][j-1] + 1);
}
}
return dp[n][n];
}
};- 时间复杂度:
, 两重循环。
- 空间复杂度:
, 开了二维数组。

京公网安备 11010502036488号