Leetcode 516. 最长回文序列
问题:给你一个字符串 s
,找出其中最长的回文子序列,并返回该序列的长度。
子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。
算法1:记忆化搜索
双指针,left 指针指向最左边,right 指针指向最右边,当 s [ left ] == s [ right ] 时,回文数 + 2 ,两者不相等时,左指针 left 右移或者右指针 right 左移。
时间复杂度:O(n²) 。
其中 n 为 s 的长度。动态规划的时间复杂度 = 状态个数 × 单个状态的转移个数。本题中状态个数等于 O(n²),而单个状态的转移个数为 O(1),因此时间复杂度为 O(n²) 。
空间复杂度:O(n²) 。
保存多少状态,就需要多少空间。
代码:
class Solution {
public:
int longestPalindromeSubseq(string s) {
int n = s.length();
vector<vector<int>> memo(n,vector<int>(n,-1));
auto dfs = [&](auto &&dfs,int left,int right) -> int{
if(left > right) return 0;
if(left == right) return 1;
int& res = memo[left][right];
if(res != -1) return res;
if(s[left] == s[right]) return dfs(dfs,left + 1,right - 1) + 2;
return max(dfs(dfs,left + 1,right),dfs(dfs,left,right - 1));
};
return dfs(dfs,0,n - 1);
}
};
算法2:1:1 翻译成递推
如何思考循环顺序?什么时候要正序,什么时候要倒序?
这里有一个通用的做法:盯着状态转移方程,想一想,要计算 dp [ i ] [ j ] ,必须先把 dp [ i + 1 ] [ ⋅ ] 算出来,那么只有 i 从大到小枚举才能做到。对于 j 来说,由于在计算 dp [ i ] [ j ] 的时候,需要用到 dp [ i ] [ j − 1 ] ,也就是必须先把 dp [ i ] [ j − 1 ] 算出来,所以 j 必须从小到大枚举。
时间复杂度:O(n²) 。
其中 n 为 s 的长度。动态规划的时间复杂度 = 状态个数 × 单个状态的转移个数。本题中状态个数等于 O(n²),而单个状态的转移个数为 O(1),因此时间复杂度为 O(n²)。
空间复杂度:O(n²) 。
代码:
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;
for(int j = i + 1;j < n;j++)
dp[i][j] = s[i] == s[j] ? dp[i + 1][j - 1] + 2 : max(dp[i][j - 1],dp[i + 1][j]);
}
return dp[0][n - 1];
}
};
算法3:空间优化
把 dp 数组的第一个维度去掉。相当于把 dp [ i ] 和 dp [ i + 1 ] 保存到同一个一维数组中。
但一个萝卜一个坑,dp [ j − 1 ] 要么保存的是 dp [ i + 1 ] [ j − 1 ] ,要么保存的是 dp [ i ] [ j − 1 ] ,怎么妥当地处理新旧数据?对于本题来说,可以用变量 pre 记录 dp [ i + 1 ] [ j − 1 ] 的值。计算到 dp [ j ] 时,dp [ j − 1 ] 保存的是新数据 dp [ i ] [ j − 1 ],旧数据 dp [ i + 1 ] [ j − 1 ] 可以从 pre 中取到。
时间复杂度:O(n²) ,其中 n 为 s 的长度。
空间复杂度:O(n) 。
代码:
class Solution {
public:
int longestPalindromeSubseq(string s) {
int n = s.length();
vector<int> dp(n);
for(int i = n - 1;i >= 0;i--){
dp[i] = 1;
int pre = 0;
for(int j = i + 1;j < n;j++){
int tmp = dp[j];
dp[j] = s[i] == s[j] ? pre + 2 : max(dp[j],dp[j - 1]);
pre = tmp;
}
}
return dp[n - 1];
}
};