算法學習--字符串--最長迴文子串

題目背景算法

給定一個字符串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;
}
相關文章
相關標籤/搜索