求最长回文串

解法一:中心扩散法

过于***,就是枚举中心点向两边拓展长度直到不相等。(时间复杂度 ,空间复杂度

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];