给你一个字符串 s ,找出其中最长的回文子序列,并返回该序列的长度。
子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。
示例 1:
输入:s = "bbbab" 输出:4 解释:一个可能的最长回文子序列为 "bbbb" 。
示例 2:
输入:s = "cbbd" 输出:2 解释:一个可能的最长回文子序列为 "bb" 。
提示:
1 <= s.length <= 1000s仅由小写英文字母组成
思路分析 #
其实有一个非常关键的思路。就是
字符串s的最长回文子序列 等价于 字符串s和s的倒序s’的最长公共子序列
简单的证明,s与s倒序的最长公共子序列一定是回文的。正着有,反着有,且相同,这就是回文的定义。
也不会有比s与s倒序的最长公共子序列更长的回文子序列了。如果有,那么s与s倒序的最长公共子序列也会变得更长。
所以我们成功的将题目转化为求s与s倒序的最长公共子序列的长度。
既然是这样,就非常方便了。直接使用二维动态规划即可轻松求解。
首先设置动态规划变量dp[n+1][n+1](预留一个空位方便计算)。
再说dp[i][j]的含义,就是s中前i位与s’中前j位的最长公共子序列。
动态规划的递推式也非常好思考。只要遇到了两个相同的字符,dp[i][j] = dp[i - 1][j - 1] + 1
否则,dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
Rust代码 #
# struct Solution;
impl Solution {
pub fn longest_palindrome_subseq(s: String) -> i32 {
let s = s.into_bytes();
let n = s.len();
let mut res = 0;
let mut dp = vec![vec![0; n + 1]; n + 1];
for i in 1..=n {
for j in 1..=n {
if s[n - i] == s[j - 1] {
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
dp[i][j] = dp[i - 1][j].max(dp[i][j - 1]);
}
}
}
dp[n][n]
}
}