迴文子串(模板)Manacher O(n)演算法__演算法

來源:互聯網
上載者:User
一、問題描述

迴文子串和迴文子序列不同:

子串,一定要連續
子序列,不一定連續

其實最長迴文子串是可以轉換成LCS來做的,具體方法就是:

將原串產生反向串,然後用dp求原串和反向串的LCS

但是這樣缺點也很明顯的是O(n*n)的複雜度,即使最佳化到:滾動數組+下標找反向串,也不能從根本上解決這個演算法的低效。

如果想在 O(n) 時間內解決迴文子串問題呢。

答案就是Manacher演算法,用一句話來概括這個演算法:

通過記錄已匹配的最右位置對應的對稱中心來跳過一些沒用的比較

在下面的代碼中,這個已匹配的最右位置就是mx,對稱中心就是id。 二、Manacher演算法模板

完整模板

int Manacher(string tmp) {    memset(P,0,sizeof(P));    int len=tmp.size();    int k;    for(k=0;k<len;k++){        s[2*k] = '#';        s[2*k+1] = tmp[k];    }    s[2*k] = '#';    s[2*k+1] = '\0';    //演算法核心    len=strlen(s);    int mx = 0; //mx 記在i之前的迴文串中,延伸至最右端的位置    int id=0; //id 記下取得這個最優mx時的 座標值    for (int i=0;i<len;++i) {        if( i < mx ) //在當前最優邊界左邊            P[i] = min(P[2*id-i],mx-i);        else            P[i]=1;        for (;s[i-P[i]] == s[i+P[i]] && s[i-P[i]] != '\0' && s[i+P[i]] != '\0' ; ) //對於超出mx或者P[j]邊界的計算            P[i]++;        if( P[i]+i > mx ) { //當前最佳情況,update mx和id            mx = P[i] + i; //最右端情況            id = i; //記下座標        }    }    int res = 0;    for (int i=0;i<len;++i) {        res = max(res,P[i]);    }    return res-1;}
三、演算法思想 1、預先處理

演算法在字串開頭、結尾、字元之間都插入特殊字元(這裡用’#’),這樣就巧妙的把奇數的迴文串和偶數的迴文串統一起來考慮了(統一為奇數),不然處理迴文串會很麻煩。

比如:abc 變成 #a#b#c# 這樣有7個字元。
abcd 變成 #a#b#c#d# 有9個字元。

可能有疑問:預先處理成這樣,後面不是要算迴文子串長度的時候,又要減去這些特殊字元嗎。

其實不會,因為演算法用 P[i] 來記錄 i 這個 pos 的最長單臂子串長度,比如 #a#b#c#b#a# 這個單臂子串長度為 #a#b#c,即長度為6,

而這裡有一個很好的性質,P[i]-1 就是該迴文子串在原串中的長度。即 abcba 就是5 2、核心演算法

其實Manacher演算法的核心代碼很簡單,但是理解起來感覺相當精妙,果然是演算法是代碼寫成的詩歌

核心代碼:

//演算法核心len=strlen(s);int mx = 0; //mx 記在i之前的迴文串中,延伸至最右端的位置int id=0; //id 記下取得這個最優mx時的 座標值for (int i=0;i<len;++i) {    if( i < mx ) //在當前最優邊界左邊        P[i] = min(P[2*id-i],mx-i); //算出對稱初始值    else        P[i]=1; //如果超出mx邊界,P[i]初始值為1    for (;s[i-P[i]] == s[i+P[i]] && s[i-P[i]] != '\0' && s[i+P[i]] != '\0' ; ) //對於超出mx或者P[j]邊界的計算        P[i]++;    if( P[i]+i > mx ) { //當前最佳情況,update mx和id        mx = P[i] + i; //最右端情況        id = i; //記下座標    }}

演算法開始是會通過一個if else判斷來決定P[i]的初始值。

演算法的關鍵點就在這裡:

if( i < mx ) //在當前最優邊界左邊    P[i]= min(P[2*id-i],mx-i); //算出對稱初始值else    P[i]=1; //如果超出mx邊界,P[i]初始值為1

那為什麼是 min(P[2*id-i],mx-i) 呢。 3.2.1 取 P[2*id-i],即 P[2*id-i] <= mx-i

這種情況下,整個 中心i(或中心j) 的子串都涵蓋在 中心為 id,長度為 mx-id 的巨型臂膀下,由於 id為中心的完全對稱,必然 P[j] == P[i], 3.2.2 取 mx-i,即 P[2*id-i] >= mx-i

這種情況下,是 id 的臂展無法完全包裹 以i為中心的臂展時:

根據對稱性可知,圖中兩個綠框所包圍的部分是相同的,也就是說以 i 為中心的迴文子串,其向右至少會擴張到mx的位置,也就是說 P[i] >= mx - i。至於mx之後的部分是否對稱,就只能一個一個匹配了。

另外特別要注意的是,要注意設定“哨兵”,也就是自己設定特殊字元或者’\0’代表邊界,不然後面的

for (;s[i-P[i]] == s[i+P[i]] ; P[i]++);

就會越界。

給出我畫出的原手稿

下圖中,當i為13時,臂展最大,但是這個字元的P[i]初始化為6,即 i = 5那個點 ‘c’。 參考資料

[1] http://www.cnblogs.com/biyeymyhjob/archive/2012/10/04/2711527.html

[2] http://blog.csdn.net/ggggiqnypgjg/article/details/6645824/

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.