算法学习--字符串--最长回文子串

题目背景算法

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


解法ide

1.枚举法
spa

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

(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*kget

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

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


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]

16b714ba79e19a1bc36ce92b8f4e4751.png-wh_

(2)mx-i<P[j],也即j-my<P[j],这种状况是部分回文里嵌回文,没法直接获得P[i],但至少能够得知P[i]>mx-i,这样也能够省下很多计算,直接从mx-i开始用枚举法往外扩张求得最后值

c7041224d742932f97e10986ef886089.png-wh_

最后贴一下代码

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;
}
相关文章
相关标签/搜索