回文子串、回文子序列相关题目
回文子串是要连续的,回文子序列可不是连续的。
516. 最长回文子序列
dp数组含义: d p [ i ] [ j ] dp[i][j] dp[i][j] 表示子序列 s [ i , j ] s[i,j] s[i,j] 中的最长回文子序列的长度。
dp数组初始化:子序列长度为 1 时,最长回文子序列的长度就是 1, 即 s [ i , i ] = 1 s[i,i]=1 s[i,i]=1
递推公式:
- 如果 s [ i ] = = s [ j ] s[i]==s[j] s[i]==s[j] ,那么相比于 s [ i + 1 , j − 1 ] s[i+1,j-1] s[i+1,j−1] , s [ i , j ] s[i,j] s[i,j] 的最长子序列的长度增加了 2(首尾)
- 如果 s [ i ] ≠ s [ j ] s[i]\ne s[j] s[i]=s[j],那么 s [ i , j ] s[i,j] s[i,j] 的最长子序列的长度就是 s [ i + 1 , j ] , s [ i , j − 1 ] s[i+1,j],s[i,j-1] s[i+1,j],s[i,j−1] 中的较大者(取首或者取尾)
遍历顺序:
这里顺序比较讲究,我们知道,动态规划解法的遍历顺序需要遵循的原则是要按照递推公式的依赖关系,即递推公式中计算 dp 数组中的某个值时一定要保证它所依赖的值已经在 dp 数组中被计算好了。
在本题中,我们看到递推公式中, d p [ i ] [ j ] dp[i][j] dp[i][j] 的值依赖于三个值: d p [ i + 1 ] [ j − 1 ] dp[i+1][j-1] dp[i+1][j−1] 、 d p [ i + 1 ] [ j ] dp[i+1][j] dp[i+1][j]、 d p [ i ] [ j − 1 ] dp[i][j-1] dp[i][j−1],我们所选择的遍历顺序需要保证在计算 d p [ i ] [ j ] dp[i][j] dp[i][j] 时这三个值都已经计算过了,那么很明显的, i i i 要从大往小, j j j 要从小往大。
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 i=n-1; i>=0; --i) {
for (int j=i+1; j<n; ++j) {
if (s[i] == s[j]) dp[i][j] = dp[i+1][j-1] + 2;
else dp[i][j] = max(dp[i+1][j], dp[i][j-1]);
}
}
return dp[0][n-1];
}
};
5. 最长回文子串
dp数组含义: d p [ i ] [ j ] dp[i][j] dp[i][j] 表示子串 s [ i , j ] s[i, j] s[i,j] 是否为回文串。
dp数组初始化:长度为 1 的子串,即 s [ i , i ] s[i,i] s[i,i] 一定是回文串。
递推公式:
- 如果 s [ i ] ≠ s [ j ] s[i]\ne s[j] s[i]=s[j],那么 s [ i , j ] s[i,j] s[i,j] 一定不是回文串;
- 如果
s
[
i
]
=
=
s
[
j
]
s[i]==s[j]
s[i]==s[j], 那要再看子串的长度:
- 如果子串长度小于等于 3, 那 s [ i , j ] s[i,j] s[i,j] 一定是回文串
- 如果子串长度大于 3,则 s [ i , j ] s[i,j] s[i,j] 是不是回文串就取决于 s [ i + 1 ] [ j − 1 ] s[i+1][j-1] s[i+1][j−1] 是不是回文串。如 sabas 是不是回文串取决于 aba 是不是回文串。
遍历顺序:
外层循环遍历子串的长度,内层循环遍历起始位置,这里也可以考虑与上面类似的遍历顺序思路,会在下一题中给出代码。
class Solution {
public:
string longestPalindrome(string s) {
int n = s.size();
if (n < 2) return s;
string ans(1, s[0]);
vector<vector<bool>> dp(n, vector<bool> (n));
for (int i=0; i<n; ++i) dp[i][i] = true;
for (int len=2; len<=n; ++len) {
for (int i=0; i<n; ++i) {
int j = i + len - 1;
if (j >= n) break;
if (s[i] != s[j]) dp[i][j] = false;
else {
if (len <= 3) dp[i][j] = true;
else dp[i][j] = dp[i+1][j-1];
}
if (dp[i][j] && len>ans.size()) ans = s.substr(i, len);
}
}
return ans;
}
};
647. 回文子串
思路和上一题逻辑类似
遍历顺序一,与上题一致
class Solution {
public:
int countSubstrings(string s) {
int n = s.size();
if (n < 2) return n;
vector<vector<bool>> dp(n, vector<bool> (n));
for (int i=0; i<n; ++i) dp[i][i] = true;
int ans = n;
for (int len=2; len<=n; ++len) {
for (int i=0; i<n; ++i) {
int j = i + len - 1;
if (j >= n) break;
if (s[i] != s[j]) dp[i][j] = false;
else {
if (len <= 3) {
dp[i][j] = true;
++ans;
}
else {
if (dp[i+1][j-1]) {
dp[i][j] = true;
++ans;
}
else dp[i][j] = false;
}
}
}
}
return ans;
}
};
另一种根据长度和起始位置的遍历顺序,思路类似题516:
class Solution {
public:
int countSubstrings(string s) {
int n = s.size();
int ans = 0;
vector<vector<bool>> dp(n, vector<bool> (n, false));
for (int i=n-1; i>=0; --i) {
for (int j=i; j<n; ++j) {
if (s[i] == s[j]) {
if (abs(i-j) <= 1) {
dp[i][j] = true;
++ans;
}
else {
if (dp[i+1][j-1]) {
dp[i][j] = true;
++ans;
}
}
}
}
}
return ans;
}
};