题目背景

给定一个字符串str,若子串s是回文串(如aba, abba),则称s为str的回文子串,现在需要求出str的最长回文子串


解法

1.枚举法

这种解法比暴力法好一点,就是遍历字符串每一位str[i]:

(1)首先如果是aba型,那就从i出发往两边扩张,看str[i-1]跟str[i+1]是否相等,进而扩张到str[i-k]跟str[i+k],直到str[i-(k+1)]跟str[i+(k+1)]不相等,注意这个扩张的前提的不越界,这样子就能得到最长长度是1+2*k

(2)再者还得讨论abba型,那就从i,i+1分别出发往两边扩张,看str[i-1]跟str[i+1+1]是否相等,进而扩张到str[i-k]跟str[i+1+k],直到str[i-(k+1)]跟str[i+1+(k+1)]不相等,注意这个扩张的前提的不越界,这样子就能得到最长长度是2+2*k

取(1)和(2)中较大的值,最后综合所有的i,取出最最大的值就是最长回文子串

这种解法的时间复杂度是O(n2),但是还存在一种线性的时间复杂度算法


2.Manacher算法

首先需要对字符串进行预处理,目的是要统一aba和abba的解法,做法是在每个字符串间隙和头尾都加上#

如 a b b a --> # a # b # b # a #

这样生成新的字符串S,它一定是个奇数,通过有字母的字符位可以计算aba的情况,通过#的字符位可以计算abba的情况,这样就很好把两种情况统一了

定义一个数组P[i],来记录以字符S[i]为中心的最长回文子串向左/右扩张的长度(包括S[i]),即单边的范围

接下来需要考虑的是已经P[0],P[1]...P[i-1]的情况下怎么得到P[i]

从0...i-1中取一个id,使P[id]+id最大,记做mx=P[id]+id,这个mx显然是之前的最长回文子串能够到达的最右端的范围

现在就考察i有没有落在[0,mx)区间,如果没落在那就说明之前的P[0],P[1]...P[i-1]都无法帮助计算P[i],需要用枚举法计算,如果落在,那就说明P[0],P[1]...P[i-1]至少是有价值的,进一步可以分类讨论:

设i关于id的对称点为j,满足j=2*id-i,设mx关于id的对称点my,满足my=2*k-mx

若(1)mx-i>P[j],也即j-my>P[j],这种情况就是回文里嵌回文,所以直接可得P[i]=P[j]

(2)mx-i,也即j-mymx-i,这样也可以省下不少计算,直接从mx-i开始用枚举法往外扩张求得最后值


最后贴一下代码

void Manacher(const char* str)
{
    int size = strlen(str);
    int N = 2*size+1;
    int* p = new int[N];
    int mx = 1;
    int id = 0;
    p[0] = 1;
    for(int i = 1; i < N; i++)
    {
        if(i < mx)
            p[i] = min(p[2*id-i], mx-i);
        else
            p[i] = 1;
        while((i != p[i]) && (((i+p[i]) % 2 == 1)
            || (str[(i+p[i])/2-1] == str[(i-p[i])/2-1])))
            p[i]++;
        if(mx < i + p[i])
        {
            mx = i + p[i];
            id = i;
        }
    }
    Print(str);
    Print(p, N);
    delete[] p;
}