【數據結構與算法】 Manacher 算法求最長迴文子串

求一個字符串的最長迴文子串是面試中常常出現的一道算法題,所謂的迴文串是:一個字符串正着讀和反着讀是同樣的,那它就是迴文串。下面是一些迴文串的實例。面試

12321   a    aba    abba    zhiuuihz

對於最長迴文子串問題,最簡單粗暴的辦法是:找到字符串的全部子串,遍歷每個子串以驗證它們是否爲迴文串。一個子串由子串的起點和終點肯定,所以對於一個長度爲n的字符串,共有n^2個子串。這些子串的平均長度大約是n/2,所以這個解法的時間複雜度是O(n^3)。算法

對於一個比較長的字符串,O(n^3)的時間複雜度顯然是難以接受的。下面要講的Manacher算法可以將O(n^3)降到O(n)。數組

Manacher算法

首先用一個很是巧妙的方式,將全部可能的奇數/偶數字符串都轉換成了奇數長度:在每一個字符的兩邊都插入一個特殊的符號如#。好比 abba 變成 #a#b#b#a#, aba變成 #a#b#a#。 插入的是一樣的符號,所以子串的迴文性不受影響,原來是迴文的串,插完以後仍是迴文的,原來不是迴文的,依然不會是迴文。服務器

爲了進一步減小編碼的複雜度,能夠在字符串的開始加入另外一個特殊字符,這樣就不用特殊處理越界問題,好比$#a#b#a#(注意,下面的代碼是用C語言寫就,因爲C語言規範還要求字符串末尾有一個'\0'因此正好OK,但其餘語言不加前面特殊符號可能會致使越界)。數據結構

下面以字符串12212321爲例,通過上一步,變成了 S[] = "#1#2#2#1#2#3#2#1#";
而後用一個數組 P[i] 來記錄以字符S[i]爲中心的最長迴文子串向左/右擴張的長度(包括S[i],也就是把該回文串「對摺」之後的長度),好比S和P的對應關係:ide

S  # 1 # 2 # 2 # 1 # 2 # 3 # 2 # 1 #
P  1 2 1 2 5 2 1 4 1 2 1 6 1 2 1 2 1
(p.s. 不難看出,P[i]-1正好是原字符串中迴文串的總長度)

以上面紅色的1爲例,以它爲中心的迴文字符串爲# 2#1#2#,該回文串「對摺」之後的長度爲4,所以P數組對應的值爲4。P數組其它值也是這樣計算出的。並且能夠看出,若是P[i]中最大值-1就是原字符串最大回文子串的長度。wordpress

如今問題關鍵是怎麼計算P[i]呢?該算法增長兩個輔助變量(其實一個就夠了,兩個更清晰)id和mx,其中 id 爲已知的 {右邊界最大} 的迴文子串的中心,mx則爲id+P[id],也就是這個子串的右邊界。ui

而後能夠獲得一個很是神奇的結論,這個算法的關鍵點就在這裏了:若是mx > i,那麼P[i] >= MIN(P[2 * id - i], mx - i)。就是這個串卡了我很是久。實際上若是把它寫得複雜一點,理解起來會簡單不少:編碼

//記j = 2 * id - i,即j是i關於id 的對稱點
if (mx - i > P[j]) 
    P[i] = P[j];
else       // P[j] >= mx - i 
    P[i] = mx - i; // P[i] >= mx - i,取最小值,以後再匹配更新。

固然光看代碼仍是不夠清晰,仍是藉助圖來理解比較容易。
當 mx - i > P[j] 的時候,以S[j]爲中心的迴文子串包含在以S[id]爲中心的迴文子串中,因爲 i 和 j 對稱,以S[i]爲中心的迴文子串必然包含在以S[id]爲中心的迴文子串中,因此必有 P[i] = P[j],見下圖。
【數據結構與算法】 Manacher 算法求最長迴文子串
當 P[j] >= mx - i 的時候,以S[j]爲中心的迴文子串不必定徹底包含於以S[id]爲中心的迴文子串中,可是基於對稱性可知,下圖中兩個綠框所包圍的部分是相同的,也就是說以S[i]爲中心的迴文子串,其向右至少會擴張到mx的位置,也就是說 P[i] >= mx - i。至於mx以後的部分是否對稱,就只能老老實實去匹配了。
【數據結構與算法】 Manacher 算法求最長迴文子串
對於 mx <= i 的狀況,沒法對 P[i]作更多的假設,只能P[i] = 1,而後再去匹配了。
因而代碼以下:code

//輸入,並處理獲得字符串s

int p[1000], mx = 0, id = 0;
memset(p, 0, sizeof(p));

for (i = 1; s[i] != '\0'; i++) 
{
    p[i] = mx > i ? min(p[2*id-i], mx-i) : 1;
    while (s[i + p[i]] == s[i - p[i]])
    {
        p[i++];
    }

    if (i + p[i] > mx) 
    {
        mx = i + p[i];
        id = i;
    }
}

//後面就是找出p[i]中最大值

參考文獻:
https://zhuhongcheng.wordpress.com/2009/08/02/a-simple-linear-time-algorithm-for-finding-longest-palindrome-sub-string/

推薦閱讀:

精心整理 | 歷史乾貨文章目錄
【福利】本身蒐集的網上精品課程視頻分享(上)
【數據結構與算法】 通俗易懂講解 二叉樹遍歷
【數據結構與算法】 通俗易懂講解 二叉搜索樹

專一服務器後臺技術棧知識總結分享

歡迎關注交流共同進步

【數據結構與算法】 Manacher 算法求最長迴文子串

碼農有道 coding

碼農有道,爲您提供通俗易懂的技術文章,讓技術變的更簡單!

相關文章
相關標籤/搜索