求最长回文串 解法一:中心扩散法 过于傻逼,就是枚举中心点向两边拓展长度直到不相等。(时间复杂度 $O(n^2)$,空间复杂度 $O(1)$)
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #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 ; }
解法二:动态规划 $dp[i][j] = 0/1 $ 表示子串 $i~j$ 是否为回文串。
那么,容易得到:$dp[i][j] = (dp[i+1][j-1]$ && $s[i]==s[j])$。
由于 $i$ 是由 $i+1$ 转移过来的,所以我们需要倒着遍历:
Code 1 2 3 4 5 6 7 for (int i = s.size() - 1 for (int j = i if (s[i] == s[j] && (j - i < 2 || dp[i + 1 ][j - 1 ])) { dp[i][j] = true; res = max(res, j - i + 1 ); } }
时间复杂度 $O(n^2)$,空间复杂度 $O(n^2)$
解法三:manacher Code 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 #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]++; if (i + p[i] > mx) { mx = i + p[i]; id = i; } 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
状态
$f[i][j]$ 表示 $s$ 的第 $i$ 个字符到第 $j$ 个字符组成的子串中,最长的回文序列长度是多少。
转移方程
如果 $s$ 的第 $i$ 个字符和第 $j$ 个字符相同的话
$f[i][j] = f[i + 1][j - 1] + 2$
如果 $s$ 的第 $i$ 个字符和第 $j$ 个字符不同的话
$f[i][j] = max(f[i + 1][j], f[i][j - 1])$
然后注意遍历顺序,$i$ 从最后一个字符开始往前遍历,$j$ 从 $i + 1$ 开始往后遍历,这样可以保证每个子问题都已经算好了。
初始化 $f[i][i] = 1$ 单个字符的最长回文序列是 $1$
结果 $f[0][n - 1]$
Code 1 2 3 4 5 6 7 8 9 10 for(int i = n - 1; i >= 0; i--) { dp = 1; for(int j = i + 1; j < n; j++) { if(s == s) dp = dp + 2; else dp = max(dp, dp); } } return dp;
变形2 插入最少的字符,使得字符串变成回文串。 solution 很直观的,答案就是原长度-最长回文子序列。考虑另一种区间dp的做法:
既然是区间dp,那么就是由小区间得到大区间,故最外层从小到大枚举长度,然后枚举左右端点。
$dp[i][j] = min(dp[i + 1][j] + 1, dp[i][j - 1] + 1)$ $s[i] != s[j]$
$dp[i][j] = min(dp[i + 1][j] + 1, dp[i][j - 1] + 1, dp[i + 1][j - 1])$ $s[i] == s[j]$
Code 1 2 3 4 5 6 7 8 9 for (int len = 2; len <= n; len++) { for (int l = 0; l <= n - len; l++) { int r = l + len - 1; dp = min(dp, dp) + 1; if (s == s) dp = min(dp, dp); } } return dp;