问题来源:leetcode 516。
问题定义
给定一个字符串 s
,找到其中最长的回文子序列,并返回该序列的长度。可以假设 s
的最大长度为 1000
。
示例 1:
输入:
"bbbab"
输出:
4
一个可能的最长回文子序列为 "bbbb"。
示例 2:
输入:
"cbbd"
输出:
2
一个可能的最长回文子序列为 "bb"。
提示:
1 <= s.length <= 1000
s
只包含小写英文字母
转换成最长公共子序列问题
首先将字符串反转,然后求这两个字符串的最长公共子序列便是该字符串的最长回文子序列。
class Solution {
public:
int longestPalindromeSubseq(string s) {
string s_reverse = s;
reverse(s_reverse.begin(), s_reverse.end());
return lcss(s, s_reverse);
}
int lcss(string& s1, string& s2) {
int n = s1.size(), m = s2.size();
vector<vector<int>> dp(n+1, vector<int>(m+1));
for(int i=1; i<=n; i++) {
for(int j=1; j<=m; j++) {
if(s1[i-1] == s2[j-1]) {
dp[i][j] = dp[i-1][j-1] + 1;
} else {
dp[i][j] = max(dp[i][j-1], dp[i-1][j]);
}
}
}
return dp[n][m];
}
};
直接动态规划分析
优化子结构:设原问题的字符串 S = s 1 s 2 , . . . s n S=s_1s_2,...s_n S=s1s2,...sn 的最长回文子序列为 X = x 1 , x 2 , . . . , x m X=x_1,x_2,...,x_m X=x1,x2,...,xm:
- 如果 s 1 = s n s_1 = s_n s1=sn,那么 X ′ = x 2 , . . . x n − 1 X^{'}=x_2,...x_{n-1} X′=x2,...xn−1 是子问题的字符串 S ′ = s 2 , . . . , s n − 1 S^{'}=s_2,...,s_{n-1} S′=s2,...,sn−1 的最长回文子序列,即需要求解子问题 S ′ S^{'} S′ 的最优解 X ′ X^{'} X′。反证法证明:如果 X ′ X^{'} X′ 不是 S ′ S^{'} S′ 的最优解,那么 S ′ S^{'} S′ 存在一个更长的回文子序列,在此基础上再加上 s 1 s_1 s1 和 s n s_n sn,可以得到 S S S 的一个更长的回文子序列,与 X X X 是 S S S 的一个最长的回文子序列矛盾。
- 如果 s 1 ≠ s n s_1 \neq s_n s1=sn,那么 X X X 是子问题 S 1 ′ = s 1 , . . . , s n − 1 S^{'}_1=s_1,...,s_{n-1} S1′=s1,...,sn−1 和 S 2 ′ = s 2 , . . . , s n S^{'}_{2}=s_2,...,s_n S2′=s2,...,sn 的最长回文子序列中较长的那个,即需要求解子问题 S 1 ′ S^{'}_1 S1′ 和 S 2 ′ S^{'}_2 S2′ 的最优解,同样可以通过反证法来证明。
重叠子问题:简单地通过递归算法来计算,会有子问题被重复计算。
递归地定义最优解的值:设 d p ( i , j ) dp(i,j) dp(i,j) 表示以 s i s_i si 开头,以 s j s_j sj 结尾的字符串的最长回文子序列的长度,那么:
- 如果 i > j i > j i>j,那么 d p ( i , j ) = 0 dp(i, j)=0 dp(i,j)=0;
- 如果 i = j i = j i=j,那么 d p ( i , j ) = 1 dp(i,j) = 1 dp(i,j)=1;
- 如果
i
<
j
i < j
i<j,那么:
- 如果 s i = s j s_i = s_j si=sj,则 d p ( i , j ) = 2 + d p ( i + 1 , j − 1 ) dp(i,j) = 2 + dp(i+1, j-1) dp(i,j)=2+dp(i+1,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) }
class Solution {
public:
int longestPalindromeSubseq(string s) {
int n = s.size();
vector<vector<int>> dp(n, vector<int>(n));
for(int i=0; i<n; i++) {
dp[i][i] = 1;
}
for(int l = 2; l<=n; l++) {
for(int i = 0; i<= n-l; i++) {
int j = i + l - 1;
if(s[i] == s[j]) {
dp[i][j] = 2 + dp[i+1][j-1];
} else {
dp[i][j] = max(dp[i+1][j], dp[i][j-1]);
}
}
}
return dp[0][n-1];
}
};