求最长回文串
解法一:中心扩散法
过于***,就是枚举中心点向两边拓展长度直到不相等。(时间复杂度 ,空间复杂度 )
Code
#include <bits/stdc++.h> using namespace std; string s; int check(int pos, int type) { int now, x, y; if (type) now = 0, x = pos, y = pos + 1; else now = 1, x = pos - 1, y = pos + 1; while (1) { if (x >= 0 && y < s.size() && s[x] == s[y]) now += 2; else break; x--, y++; } return now; } int main() { while (cin >> s) { int res = 1; for (int i = 1; i < s.size() - 1; i++) res = max(res, max(check(i, 0), check(i, 1))); cout << res << '\n'; } return 0; }
解法二:动态规划
表示子串 是否为回文串。
那么,容易得到: && 。
由于 是由 转移过来的,所以我们需要倒着遍历:
Code
for (int i = s.size() - 1; i >= 0; i--) for (int j = i; j < s.size(); j++) { if (s[i] == s[j] && (j - i < 2 || dp[i + 1][j - 1])) { dp[i][j] = true; res = max(res, j - i + 1); } }
时间复杂度 ,空间复杂度
解法三:manacher
Code
#include <stdio.h> #include <algorithm> using namespace std; const int N = 2e6 + 5; int p[N]; char s[N], t[N]; int manacher() { int l = 0; t[l++] = '$'; t[l++] = '#'; for (int i = 0; s[i]; i++) { t[l++] = s[i]; t[l++] = '#'; } t[l++] = '@'; int id = 0, mx = 0, index = 0, maxlength = -1; for (int i = 1; i < l; i++) { p[i] = mx > i ? min(p[2 * id - i], mx - i) : 1; /// 向左右两边延伸,扩展右边界 while (t[i + p[i]] == t[i - p[i]]) p[i]++; /// 如果回文子串的右边界超过了mx,则需要更新mx和id的值 if (i + p[i] > mx) { mx = i + p[i]; id = i; } /// 如果回文子串的长度大于maxLength,则更新maxLength和index的值 if (maxlength < p[i] - 1) { maxlength = p[i] - 1; index = i; } } int start = (index - maxlength) / 2; /// 记录起始位置 return maxlength; } int main() { while (~scanf("%s", s)) printf("%d\n", manacher()); return 0; }
变形1
求最长回文子序列
solution
状态
表示 的第 个字符到第 个字符组成的子串中,最长的回文序列长度是多少。
转移方程
如果 的第 个字符和第 个字符相同的话
-
如果 的第 个字符和第 个字符不同的话
-
然后注意遍历顺序, 从最后一个字符开始往前遍历, 从 开始往后遍历,这样可以保证每个子问题都已经算好了。
初始化 单个字符的最长回文序列是
结果
Code
for(int i = n - 1; i >= 0; i--) { dp[i][i] = 1; 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];
变形2
插入最少的字符,使得字符串变成回文串。
solution
很直观的,答案就是原长度-最长回文子序列。考虑另一种区间dp的做法:
既然是区间dp,那么就是由小区间得到大区间,故最外层从小到大枚举长度,然后枚举左右端点。
Code
for (int len = 2; len <= n; len++) { for (int l = 0; l <= n - len; l++) { int r = l + len - 1; dp[l][r] = min(dp[l + 1][r], dp[l][r - 1]) + 1; if (s[l] == s[r]) dp[l][r] = min(dp[l][r], dp[l + 1][r - 1]); } } return dp[0][n - 1];