最长回文子序列
- 给你一个字符串
s
,找出其中最长的回文子序列,并返回该序列的长度。- 子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。
示例 1:
输入:s = "bbbab"
输出:4
解释:一个可能的最长回文子序列为 "bbbb" 。
示例 2:
输入:s = "cbbd"
输出:2
解释:一个可能的最长回文子序列为 "bb" 。
提示:
1 <= s.length <= 1000
s
仅由小写英文字母组成
1)转化为最长公共子序列——样本对比
- 回文子序列最长,即和自己的反转字符串对比的最长公共子序列
class Solution {
public:
vector<vector<int>> dp;
int my_Max(int a, int b, int c) {
int t = a;
t = b>t ? b:t;
t = c>t ? c:t;
return t;
}
int longestPalindromeSubseq(string s) {
string s2 = s;
reverse(s2.begin(),s2.end());
return longestCommonSubsequence(s,s2);
}
//精确位置依赖的动态规划,自下而上
int longestCommonSubsequence(string text1, string text2) {
dp.resize(text1.size(),vector<int>(text2.size(),-1));
dp[0][0] = text1[0] == text2[0] ? 1:0;
for(int i = 1; i < text2.size(); ++i) {
if(text1[0] == text2[i]) {
dp[0][i] = 1;
}
else dp[0][i] = dp[0][i-1];
}
for(int i = 1; i < text1.size(); ++i) {
if(text1[i] == text2[0]) {
dp[i][0] = 1;
}
else dp[i][0] = dp[i-1][0];
}
for(int i = 1; i < text1.size(); ++i) {
for(int j = 1; j < text2.size(); ++j) {
int p1 = dp[i-1][j];
int p2 = dp[i][j-1];
int p3;
if(text1[i] == text2[j]) {
p3 = 1+dp[i-1][j-1];
}
else p3 = 0;
dp[i][j] = my_Max(p1,p2,p3);
}
}
return dp[text1.size()-1][text2.size()-1];
}
//暴力递归转记忆化搜索 自上而下的动态规划
int longestSubseq(string s1,string s2,int index1,int index2) {
if(dp[index1][index2] != -1) return dp[index1][index2];
if(index1 == 0 && index2 == 0) {
return dp[index1][index2] = s1[index1] == s2[index2]? 1:0;
}
if(index1 != 0 && index2 == 0) {
if(s1[index1] == s2[index2]) {
return dp[index1][index2] = 1;
}
else return dp[index1][index2] = longestSubseq(s1,s2,index1-1,index2);
}
if(index1 == 0 && index2 != 0) {
if(s1[index1] == s2[index2]) {
return dp[index1][index2] = 1;
}
else return dp[index1][index2] = longestSubseq(s1,s2,index1,index2-1);
}
int p1 = longestSubseq(s1,s2,index1-1,index2);
int p2 = longestSubseq(s1,s2,index1,index2-1);
int p3;
if(s1[index1] == s2[index2]) p3 = 1+longestSubseq(s1,s2,index1-1,index2-1);
else p3 = 0;
return dp[index1][index2] = my_Max(p1,p2,p3);
}
};
2)从本身左右开始向中间考虑——范围缩小
- 暴力递归
class Solution {
public:
int my_Max(int x, int y, int z) {
int res = x;
res = y > res? y:res;
res = z > res? z:res;
return res;
}
int longestPalindromeSubseq(string s) {
return longest(s,0,s.size()-1);
}
int longest(string s, int left, int right) {
//left=right时,即一个字符,返回1
if(left == right) return 1;
//越界返回0
if(left > right) return 0;
//否则考虑三种情况
//p1 最左边字符不考虑的子串最长长度
int p1 = longest(s,left+1,right);
//p2 最右边字符不考虑的子串最长长度
int p2 = longest(s,left,right-1);
//p3 当最左和最右相等时 2+去掉最左最右之后的子串最长长度
int p3;
if(s[left] == s[right]) {
p3 = 2+longest(s,left+1,right-1);
}
//不相等时不用考虑,因为longest(s,left+1,right-1)一定在前两种情况中
else p3 = 0;
return my_Max(p1,p2,p3);
}
};
- 转记忆化搜索
class Solution {
public:
vector<vector<int>> dp;
int my_Max(int x, int y, int z) {
int res = x;
res = y > res? y:res;
res = z > res? z:res;
return res;
}
int longestPalindromeSubseq(string s) {
dp.resize(s.size(),vector<int>(s.size(),-1));
return longest(s,0,s.size()-1);
}
int longest(string s, int left, int right) {
if(dp[left][right] != -1) return dp[left][right];
if(left == right) return dp[left][right] = 1;
if(left > right) return dp[left][right] = 0;
int p1 = longest(s,left+1,right);
int p2 = longest(s,left,right-1);
int p3;
if(s[left] == s[right]) {
p3 = 2+longest(s,left+1,right-1);
}
else p3 = 0;
return dp[left][right] = my_Max(p1,p2,p3);
}
};
- 推导位置依赖,转化动态规划
class Solution {
public:
vector<vector<int>> dp;
int my_Max(int x, int y, int z) {
int res = x;
res = y > res? y:res;
res = z > res? z:res;
return res;
}
int longestPalindromeSubseq(string s) {
int n = s.size();
dp.resize(n,vector<int>(n,0));
for(int i = 0; i < n; ++i) {
dp[i][i] = 1;
}
for(int i = 1; i < n; ++i) {
for(int j = 0; j < n-i; ++j) {
int p1 = dp[j+1][j+i];
int p2 = dp[j][j+i-1];
int p3 = 0;
if(s[j] == s[j+i]) {
p3 = 2+dp[j+1][j+i-1];
}
dp[j][j+i] = my_Max(p1,p2,p3);
}
}
return dp[0][n-1];
}
};