一、动态规划DP 回文子串问题
1、回文子串 647
dp数组如果采用一维的,很难进行推导。采用二维,一开始的想法是dp[i][j]表示s[i]~s[j]之间回文子串的个数,这样发现在推导递推公式时遇到困难,例如在s[i]==s[j]时,不知道s[i+1:j-1]是否是回文子串。所以这样的定义不合理,所以让dp[i][j]表示s[i:j]是否是回文子串就好。
这样递推公式就好推导,当s[i]==s[j],如果j-i<=2,则dp[i][j] = 1,否则dp[i][j] = dp[i+1][j-1];当s[i]!=s[j],dp[i][j] = 0。再在循环里累加这个dp[i][j]就是所有的回文子串数量。
另外,关于遍历顺序需要提一下,递推公式dp[i][j] = dp[i+1][j-1],则需要提前知道dp[i+1][j-1],所以i应该是逆序,j正序。
class Solution {
public:
int countSubstrings(string s) {
int n = s.size();
vector<vector<int>> dp(n, vector<int>(n, 0));
int ans = 0;
for(int i=n-1; i>=0; --i){
for(int j=i; j<n; ++j){
if(s[i] == s[j] && (j-i<=2 || dp[i+1][j-1])){
dp[i][j] = 1;
++ans;
}
}
}
return ans;
}
};
这题更优的解法是采用双指针,能把空间复杂度优化到O(1)。有一个元素为中心点和两个元素为中心点两种情况
class Solution {
int extend(const string &s, int i, int j){
int ans = 0;
while(i>=0 && j<s.size() && s[i--]==s[j++])
++ans;
return ans;
}
public:
int countSubstrings(string s) {
int ans = 0;
for(int i=0; i<s.size(); ++i){
ans += extend(s, i, i);
ans += extend(s, i, i+1);
}
return ans;
}
};
2、最长回文子序列 516
这题和上一题很相似,只不过由回文子串变成回文子序列。代码如下:
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-2; i>=0; --i){
for(int j=i+1; j<n; ++j){
if(s[i]==s[j] && (dp[i+1][j-1] || j-i==1)){
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];
}
};
二、写在后面
回文子串采用二维dp数组比较好理解,比较经典的转移关系是 d p [ i ] [ j ] = d p [ i + 1 ] [ j − 1 ] dp[i][j] = dp[i+1][j-1] dp[i][j]=dp[i+1][j−1]相似的形式,i逆序遍历,j正序遍历$。