首页 > 代码库 > 最长回文子串算法

最长回文子串算法

#1032 : 最长回文子串

时间限制:1000ms
单点时限:1000ms
内存限制:64MB

描述

   小Hi和小Ho是一对好朋友,出生在信息化社会的他们对编程产生了莫大的兴趣,他们约定好互相帮助,在编程的学习道路上一同前进。

   这一天,他们遇到了一连串的字符串,于是小Hi就向小Ho提出了那个经典的问题:“小Ho,你能不能分别在这些字符串中找到它们每一个的最长回文子串呢?”

   小Ho奇怪的问道:“什么叫做最长回文子串呢?”

   小Hi回答道:“一个字符串中连续的一段就是这个字符串的子串,而回文串指的是12421这种从前往后读和从后往前读一模一样的字符串,所以最长回文子串的意思就是这个字符串中最长的身为回文串的子串啦~”

   小Ho道:“原来如此!那么我该怎么得到这些字符串呢?我又应该怎么告诉你我所计算出的最长回文子串呢?

   小Hi笑着说道:“这个很容易啦,你只需要写一个程序,先从标准输入读取一个整数N(N<=30),代表我给你的字符串的个数,然后接下来的就是我要给你的那N个字符串(字符串长度<=10^6)啦。而你要告诉我你的答案的话,只要将你计算出的最长回文子串的长度按照我给你的顺序依次输出到标准输出就可以了!你看这就是一个例子。”


样例输入
3
abababa
aaaabaa
acacdas
样例输出
7
5
3

题目解析:

最长回文子串,顾名思义,即是一个字符串的子串,且该子串满足回文串要求,比如abba, aba等。

思路:

先考虑长度为奇数的回文,至于偶数的一会儿再提。

1,枚举以字符串的每一个字符为中心的回文子串长度,比如有个子串:aabac,分别以第一个到第五个字符为中心,枚举长度为奇数的回文子串。记录最长回文子串长度。

2,对于aaaaaaaaaaab,这类字符串,由于有很多相同字符,因此在枚举的过程中,也许可以有点优化。具体优化思路如下:

技术分享

上图中,f(i)代表以字符 i 为中心的最长回文子串,根据以上信息,可以推断出A[5] = A[3], A[3] = A[1], A[1] = A[7], 因此,A[5] = A[7],于是 f(6) >= 3,因此在枚举的时候,针对以A[7]为中心的回文子串时,只需要考虑A[4] = A[8] ?,如果等于继续判断A[3] = A[9] ?,如果不等于,那么就可以将此时的最长回文子串记为f(6)的值了。

事实上,用 i 代表6,j 代表4,于是有:

f(i) >= min{ f(2*j-i), f(j)-2(i-j) }

其中,f(2*j-i)即为图中的f(2)了,f(j) - 2(i-j)为 f(4)可以覆盖到f(6)的长度

针对上式,做一些变形:

f(i) + 2*i >= min{ f(2*j-i) + 2(2*j-i) + 4(i-j), f(j) + 2*j },i > j

可以看到 f(2*j-i) + 2(2*j-i) + 4(i-j) > f(2*j-i) + 2(2*j-i),因此f(i) + 2*i是递增的,只需要记录一个j,使得f(j) + 2*j 是最大的,然后就可以得出f(i)的最小值了,在枚举的时候,利用此信息即可达到消减计算量的作用。


针对长度为偶数的回文串的情况,基本雷同:

技术分享


最后附上代码以供分析:

#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <string>

using namespace std;

#define N 1000010

class Solution {
public:
    char buf[N];
    int f[N];
    void solve() {
        int kase;
        scanf("%d", &kase);
        while (kase--) {
            scanf("%s", &buf);
            int longestLength = longestPalindrome(buf);
            printf("%d\n", longestLength);
        }
    }
    int longestPalindrome(char buf[]) {
        int n = strlen(buf);
        int j = 0, i, k;

        // in case the length of palindrome is odd
        f[0] = 1;
        int result = 1;
        for (i = 1; i < n; i++) {
            f[i] = min(f[2*j-i] + 2*i, f[j]+2*j) - 2*i;
            if (f[i] < 0) f[i] = 1;
            for (int left = i - f[i]/2 - 1, right = i + f[i]/2 + 1; left >= 0, right < n; left--, right++) {
                if (buf[left] == buf[right]) f[i] += 2;
                else break;
            }
            if (f[i] + 2*i > f[j] + 2*j) j = i;
            result = max(result, f[i]);
        }

        // in case the length of palindrome is even
        f[0] = 0; j = 0;
        for (i = 0; i < n-1; i++) {
            f[i] = min(f[2*j-i], f[j]-2*i+2*j);
            if (f[i] < 0) f[i] = 0;
            for (int left = i - f[i]/2, right = i + 1 + f[i]/2; left >= 0 , right < n; left--, right++) {
                if (buf[left] == buf[right]) f[i] += 2;
                else break;
            }
            if (f[i] + 2*i > f[j] + 2*j) j = i;
            result = max(result, f[i]);
        }
        return result;
    }
};

int main() {
    Solution solution;
    solution.solve();
    return 0;
}


最长回文子串算法