给你一个长度不超过1000的字符串,判断该字符串包含多少回文子串。
暴力法: 枚举子串,判断是否回文
动态规划:定义状态dp[i, j]为子串[i, j]是否为回文串。(这个状态定义在许多回文串问题中都有用到)
递归方程为
dp[i, j] = j == i+1 ? s[i] == s[j] : dp[i+1, j-1] && s[i] == s[j]
子串[i, j]为回文串的要求是s[i] == s[j] 并且 dp[i+1, j-1]也是回文子串。
class Solution {
public:
int countSubstrings(string s) {
int n = s.size();
vector<vector<int>> dp(n, vector<int>(n, 0));
for (int i = 0; i < n; i++)
dp[i][i] = 1;
int res = n;
for (int l = 2; l <= n; l++) {
for (int i = 0; i+l-1 < n; i++) {
int j = i+l-1;
if (j == i+1) dp[i][j] = s[i] == s[j];
else dp[i][j] = dp[i+1][j-1] && s[i] == s[j];
res += dp[i][j];
}
}
return res;
}
};
求给定字符串的最长回文子序列
定义dp[i, j]代表字符串s[i, j]内的最长回文子序列的长度。
递推公式:
if s[i] == s[j]:
dp[i, j] = j == i+1 ? 2 : dp[i+1, j-1] + 2;
else:
dp[i, j] = max(dp[i+1, j], dp[i, j-1]);
很像LCS问题的递推公式
class Solution {
public:
int longestPalindromeSubseq(string s) {
int len = s.size();
vector<vector<int>> isPalindrome(len, vector<int>(len, false));
for (int i = 0; i < len; i++) {
isPalindrome[i][i] = 1;
}
for (int l = 2; l <= len; l++) {
for (int i = 0; i < len+1-l; i++) {
int j = i+l-1;
if (s[i] == s[j]) {
isPalindrome[i][j] = l == 2 ? 2 : isPalindrome[i+1][j-1]+2;
} else {
isPalindrome[i][j] = max(isPalindrome[i+1][j], isPalindrome[i][j-1]);
}
}
}
return isPalindrome[0][len-1];
}
};
求给定字符串的最长回文子串
如果仍然用之前的dp方法,先求出dp[i, j],然后再从最长的子串开始找,复杂度为O(n2),在leetcode里可以ac,但是当字符串长度为1e6级别时显然就超时了(hdu 3068)。这时候就有马拉车算法,一个O(n)时间解决该问题的算法。
Manacher算法参考链接:
1. Manacher算法
2. hdu3068之manacher算法+详解
3. Manacher
4. manacher(马拉车)算法详解+例题一道【bzoj3790】【神奇项链
5. 我相信聪明的你经过以上博客一定对该算法有了大概的了解,总结来说,记住几个辅助数组与变量
mx - 所有回文串中所能延伸到最右端的位置
id - 上述mx的所对应回文串的中心点
p[i] - 以i开始,到以i为中心的回文串的最右端的长度
经过预处理(添加'#')让所有的回文串都变成了奇数
class Solution {
public:
string longestPalindrome(string s) {
string str = "$"; // 开头添加一个字符是为了在while循环中不用担心越界问题
for (auto c : s) {
str.push_back('#');
str.push_back(c);
}
str.push_back('#');
vector<int> p(str.size(), 0);
int mx = 0, id = 0, ans = 0, left = 0;
for (int i = 1; i < str.size(); i++) {
if (i < mx)
p[i] = min(p[2*id-i], mx-i);
else
p[i] = 1;
while (str[i-p[i]] == str[i+p[i]]) p[i]++;
if (mx < i+p[i]) {
mx = i+p[i];
id = i;
}
if (p[i] > ans) {
ans = p[i];
left = i-p[i]+1;
}
}
if (left == '#') left++;
left /= 2;
return s.substr(left, ans-1);
}
};
1.LeetCode 730. Count Different Palindromic Subsequences
题解很清晰,有图。这道题需要过段时间二刷。
class Solution {
public:
int countPalindromicSubsequences(string s) {
long long mod = 1000000007;
int n = s.size();
vector<vector<long long>> dp(n, vector<long long>(n, 0));
//dp[i][j]代表[i, j]内回文子序列的个数
for (int i = 0; i < n; i++) dp[i][i] = 1;
for (int l = 2; l <= n; l++)
for (int i= 0; i+l-1 < n; i++) {
int j = i+l-1;
if (s[i] != s[j])
dp[i][j] = dp[i+1][j] + dp[i][j-1] - dp[i+1][j-1];
else {
dp[i][j] = dp[i+1][j-1] * 2;
int left = i+1, right = j-1;
while (left <= right && s[left] != s[i]) left++;
while (right >= left && s[right] != s[i]) right--;
if (left > right) {
//there is no s[i] in s[i+1:j-1]
dp[i][j] += 2;
} else if (left == right) {
//there is one s[i] in s[i+1:j-1]
dp[i][j] += 1;
} else if (left < right) {
//there is more than one s[i] in s[i+1:j-1]
dp[i][j] -= dp[left+1][right-1];
}
}
dp[i][j] = (dp[i][j] + mod) % mod;
}
return dp[0][n-1];
}
};
总结:
花了大概两天的时间,做了这几道题目。这里提出一点要求,在做第一遍的时候觉得磕磕绊绊,思路或者代码不是那么好写的或者经常WA的,要收集整理过段时间从新从0开始做一遍。