【Manacher算法】最長子迴文串

【Manacher算法】python

  這個算法用來找出一個字符串中最長的迴文子字符串。算法

  若是採起暴力解最長迴文子字符串問題,大概能夠有兩種思路:1. 遍歷出全部子字符串找其中最長的迴文 2. 從每一個字符做爲中心,向兩邊擴散看是否迴文。 第二種比第一種稍微高明一點,可是整體的複雜度仍是O(n^2)的。數組

  而Manacher算法能夠作到O(n)時間複雜度,O(n)空間複雜度。函數

■  思路&描述佈局

  迴文字符串有一個比較麻煩的地方,就是迴文串有偶迴文和奇迴文兩種,分別舉例ABBA和ABCBA。這種區別可能要讓咱們在程序中額外伸出一個判斷分支來,不是很方便。因此Manacher算法的第一步就是預處理字符串,將原先的字符串全部字符中間再加上頭尾兩端都加上一個特殊符號,這樣就能夠把全部可能的迴文串都變成了奇迴文串,方便處理:編碼

  如ABBA變成#A#B#B#A#,ABCBA變成#A#B#C#B#A#。此外,通常實踐中爲了保證邊界上也能統一,還會額外在頭上(理論上字符串尾也能夠加,可是目前尾巴上確定是#,大不了咱們遍歷的時候最後這輪以這個#字符爲基礎的遍歷不去作了,這樣就能夠避免尾部邊界出錯)加上一個$或者其餘有別於#的特殊符號表示字符串的開始。通過這樣預處理以後的字符串能夠拿來被manacher算法處理。spa

  接下來,基本思路確定仍是要找出以每一個字符爲中心時,迴文串最長能夠作到多少。只是一個個去遍歷太暴力了,這裏能夠借鑑一下動態規劃中的一點當心思,也就是說咱們能不能利用已有的信息(固然這部分信息是須要在以前的分析過程當中手動保存下來的)來更加方便地推出咱們未知的信息。code

  好比咱們能夠建立一個數組p,針對被加工過字符串s,p的長度被設定爲和s等長,且p中保存的內容,是以對應原s字符串中那個字符爲中心,其最大回文子字符串的半徑長度。因爲s中全部迴文串都是奇迴文串,因此咱們所說的半徑是指從迴文串的左端開始到中心(算入中心)的長度。如#a#b#a#的半徑是4,#a#b#b#a#的半徑是5。blog

  那麼如何基於一些現有信息推斷新的信息呢?考慮這樣一種情形:假如咱們以i變量做爲向右遍歷的下標,逐漸向右遍歷肯定了一個迴文子串。這個子串的中心和半徑兩個參數都是能夠明確的。不妨稱中心的下標爲id,稱迴文串最右端下標爲mx(這也是後續編碼過程當中須要使用的兩個輔助變量)。肯定完id和mx以後咱們繼續往右推移i。字符串

  當咱們來到一個新的i時,若是它還在mx範圍內,此時能夠注意到,其實存在一個點j,j和i關於id對稱,因爲id,mx的迴文特性,因此j的迴文串很大可能就是i的迴文串。這種可能成爲肯定只須要一個條件,那就是p[j]的取值,沒有超出id,mx這個迴文串的範圍。下面是盜來的圖,說明了i,j,id,mx以及mx'(mx關於id的對稱點)的佈局。

  

  那麼若是p[j] > j - mx',即j對應的迴文串已經超出了mx的範圍,此時改怎麼辦?很顯然,在mx'到j的這段內容,因爲id,mx的迴文性,仍是能夠對應到i到mx這段內容的。至於mx以後的內容,只能再去一個個字符遍歷過去看能不能符合迴文。

  總的來講,當i仍然小於mx時,i的取值應該能夠取min(p[j],mx-i),當取p[j]的時候,p[i]就是p[j]的值。當取mx-i的時候,mx-i還只是一個基礎值,還須要進一步處理來得到準確值。

  尚未討論完,剛纔說的都是i<mx的狀況,若是此時已經遍歷到超過mx了怎麼辦?此時因爲沒有任何既存的迴文串性質能夠利用,因此只能老老實實向外一個個字符擴散判斷迴文性。比較好的一點是,以前討論過的關於i<mx的兩種取值可能,也均可以(或必要)作這個擴散。

  爲了保證遞推的連續性,不論上上述哪一種可能,得到到i的迴文串以後,都須要將id和mx進行更新。應該注意,id和mx並非咱們最後要求的最長迴文子串的屬性,而只是當前已經遍歷過的最靠右的一個迴文子串的屬性。

  而要求最長迴文子串的屬性咱們能夠再維護一個相似於longestInfo之類的變量,每找出一個迴文子串後判斷它是否是最長的。全部循環結束後去這個變量裏面取值就行了。

 

■  編碼實現

  下面是manacher算法的python實現:

def longestPalindrome(s):
    """
    :type s: str
    :rtype: str
    """
    s = '$#' + '#'.join(list(s)) + '#'    # 預處理
    p = [0] * len(s)
    longestInfo = [0,0]
    i,currRes,currMax = 1,0,0  # 因爲s[0]是$,因此i從1開始取值。id和mx分別換了個名字currRes和currMax
    while i < len(s):
        if i > currMax:    # i已經超出mx的狀況
            p[i] = 1
        else:    # i未超出mx,再分紅兩種狀況,體如今min函數中
            j = 2*currRes - i
            p[i] = min(p[j],currMax-i)
        # 此時p[i]並不必定已經正確,除了決定p[i]=p[j],其餘兩個分支都只是給了p[i]一個基準值
        while i-p[i]>0 and i+p[i]<len(s) and s[i-p[i]] == s[i+p[i]]:
            # 進行一個個字符向外擴散的迴文性檢查
            # 注意爲了防止越界訪問,還要有邊界判斷條件
            p[i] += 1
        # 這時p[i]才獲得了最終肯定的值 接下來就是將其相關屬性與已有的currRes和currMax比較,看是否須要更新
        if i + p[i] > currMax:
            # 更新id和max
            currRes = i
            currMax = i + p[i] - 1

        if p[i] > longestInfo[1]:
            # 更新最終結果值
            longestInfo = i,p[i]

        i += 1
    center = longestInfo[0]
    radius = longestInfo[1] - 1    # 注意,半徑把對稱中心自己算進去了,因此減一
    return s[center-radius:center+radius+1].replace('#','')  # 直接replace去掉全部輔助字符,獲得的就是原字符串的結果了。
相關文章
相關標籤/搜索