最長回文子串——Manacher算法


Manacher算法,又叫“馬拉車”算法,可以在時間復雜度為O(n)的情況下求解一個字符串的最長回文子串長度的問題。

一、回文子串的一般解法

比較簡單的思路是將字符串的每一個字符作為回文子串的中心對稱點,每次保存前面求得的回文子串的最大值,最后得到的就是最長的回文子串的長度,這種方式的時間復雜度是O(n^2)。在求解過程中,基數的回文子串與偶數的回文子串是不一樣的。比如最長回文子串為aba,對稱中心就是b,如果最長回文子串為abba,則對稱中心應該為兩個b之間,為了解決這個問題,可以在每個字符兩邊加上一個符號,具體什么符號(是字符串里面的符號也行)對結果沒有影響,比如加上“#”,則上述的兩個序列變成了#a#b#a#和#a#b#b#a#,求出的長度分別為6和9,再除以2就可以得到最后的結果3和4。這種方式的時間復雜度太高,下面介紹時間復雜度為O(n)的Manacher算法。

二、Manacher算法中的基礎概念

1、回文半徑數組radius

回文半徑數組radius是用來記錄以每個位置的字符為回文中心求出的回文半徑長度,如下圖所示,對於p1所指的位置radius[6]的回文半徑是5,每個位置的回文半徑組成的數組就是回文數組,所以#a#c#b#b#c#b#d#s#的回文半徑數組為[1, 2, 1, 2, 1, 2, 5, 2, 1, 4, 1, 2, 1, 2, 1, 2, 1]。

 

2、最右回文右邊界R

一個位置最右回文右邊界指的是這個位置及之前的位置的回文子串,所到達的最右邊的地方。比如對於字符串#a#c#b#b#c#b#d#s#,求它的每個位置的過程如下:

 

最右回文右邊界R過程

 

最開始的時候R=-1,到p=0的位置,回文就是其本身,最右回文右邊界R=0;p=1時,有回文串#a#,R=2;p=2時,R=2;P=3時,R=6;p=4時,最右回文右邊界還是p=3時的右邊界,R=6,依次類推。

3、最右回文右邊界的對稱中心C

就是上面提到的最右回文右邊界的中心點C,如下圖,p=4時,R=6,C=3

 
最右回文右邊界的對稱中心C

三、Manacher算法的流程

首先大的方面分為兩種情況:

第一種情況:下一個要移動的位置在最右回文右邊界R的右邊。

比如在最開始時,R=-1,p的下一個移動的位置為p=0,p=0在R=-1的右邊;p=0時,此時的R=0,p的下一個移動位置為p=1,也在R=0的右邊。

在這種情況下,采用普遍的解法,將移動的位置為對稱中心,向兩邊擴,同時更新回文半徑數組,最右回文右邊界R和最右回文右邊界的對稱中心C。

第二種情況:下一個要移動的位置就是最右回文右邊界R或是在R的左邊

在這種情況下又分為三種:

1、下一個要移動的位置p1不在最右回文右邊界R右邊,且cL<pL。

p2是p1以C為對稱中心的對稱點;

pL是以p2為對稱中心的回文子串的左邊界;

cL是以C為對稱中心的回文子串的左邊界。

這種情況下p1的回文半徑就是p2的回文半徑radius[p2]。

 
p1<=R且cL<pL

2、下一個要移動的位置票p1不在最右回文右邊界R的右邊,且cL>pL。

p2是p1以C為對稱中心的對稱點;

pL是以p2為對稱中心的回文子串的左邊界;

cL是以C為對稱中心的回文子串的左邊界。

這種情況下p1的回文半徑就是p1到R的距離R-p1+1。

 
p1<=R且cL>pL

3、下一個要移動的位置票p1不在最右回文右邊界R的右邊,且cL=pL;

p2是p1以C為對稱中心的對稱點;

pL是以p2為對稱中心的回文子串的左邊界;

cL是以C為對稱中心的回文子串的左邊界。

這種情況下p1的回文半徑就還要繼續往外擴,但是只需要從R之后往外擴就可以了,擴了之后更新R和C。

 
p1<=R且cL==pL

四、Manacher時間復雜度分析

從上面的分析中,可以看出,第二種情況的1,2的求某個位置的回文半徑的時間復雜度是O(1),對於第一種情況和第二種情況的3,R是不斷的向外擴的,不會往回退,而且尋找回文半徑時,R之內的位置是不是進行判斷的,所以對整個字符串而且,R的移動是從字符串的起點移動到終點,時間復雜度是O(n),所以整個manacher的時間復雜度是O(n)。

五、Manacher的代碼實現

#include<iostream>
#include<vector>
using namespace std;

int Manacher(string str){
    int str_len = str.size();
    vector<char> tmp;
    tmp.push_back('$');
    int len[2*str_len];
    for(int i = 1;i < str_len;i++){
        tmp.push_back('#');
        tmp.push_back(str[i]);
    }
    tmp.push_back('#');
    for(char i : tmp)
        cout << i;
    cout << endl;
    int mx = 0;
    int max_len = -1;
    int mid = 0, pos = 0;
    for(int i = 1; tmp[i]; i++)
    {
        if(i < mx)
            len[i] = min(len[2*mid-i],mx-i);
        else
            len[i] = 1;

        while(tmp[i-len[i]] == tmp[i+len[i]])
            len[i]++;

        if(len[i]+i > mx){
            mx = len[i]+i;
            mid = i;
        }
        if(max_len <= len[i]-1)
        {
            max_len = len[i]-1;
            pos = i;
        }
       // maxlen = max(maxlen,len[i]-1);
    }
    for(int i=pos-max_len; i<pos+max_len; ++i) {
        if (tmp[i] != '#')
            cout << tmp[i];
    }
    cout << endl;
    return max_len;
}
int main(){
    string str;
    cin >> str;
    cout << Manacher(str);
    return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM