最長回文子串(Longest Palindromic Substring)


一個「對稱」的序列,就可稱為回文序列,譬如:aba,abba 等。詳細介紹參看: http://zh.wikipedia.org/wiki/%E5%9B%9E%E6%96%87%E6%95%B0

最長回文子串問題是要求在給出的一個序列中,找到最長的回文字串。譬如:一個序列 cabccba,它的最長回文子串是 abccba。

暴力

暴力窮舉可以解決問題。三個循環窮舉所有可能的序列。

for i in range(0,len(str))
     for j in range(i,len(str)
               is_palindromic_number(i,j)//這里有個循環

但算法的復雜度是 O(n^3)。

一個更好的思路

在面試的是被問到這個題目,我拙計,下面是當天聊天中給出的思路:

2013-06-10

搗亂 9:52:08

開始想到遍歷,不放過任何一個回文中心,計算最大回文串,但欠妥,效率低。

回文串的難處在回文串中有回文串。

另一個是用棧,描述一下:

用一個棧,不斷往里壓串種的元素,如果發現回文串,不壓棧而且要彈出棧里的元素,並相應的記錄回文串的位置和大小(可用一個數組來存儲)

當發現「回文串 1 中存在回文串 2,回文串 3,回文串 4,...回文串 N」的情況,要作檢測,具體是看回文串 2 和回文串 N ,回文串 3 和回文串 N-1 是否對稱且相等。譬如:

d abc cba abc cba d

是回文串中有回文串的情況。

abc 和 cba 是回文串,接下來的又是一樣。所以后來棧是這樣:d,只剩一個元素,並且還有一個 d 准備壓棧,但壓棧的時候發現:因為 dd 是回文又之前處理出現了回文,因此要檢測 dd 范圍內的回文是否對稱且相等。

這種方法有窮舉的嫌疑,但規避了很多不合題意的情況。

上面的做法不穩定,因為其中的回溯過程(加粗部分)。

Manacher 線性算法

利用一個輔助數組 arr[n],其中 arr[i] 記錄的是以 str[i] 為中心的回文子串長度。當計算 arr[i] 的時候,arr[0...i-1] 是已知並且可被利用的。Manacher 核心在於:用 mx 記錄之前計算的最長的回文子串長度所能到達的最后邊界,用 id 記錄其對應的中心,可以利用回文子串中的回文子串信息。

lps02

假設 id 與 mx 已經得出,當計算以 str[i] 為中心回文子串長度時,因為已經可以確定綠色部分已經是回文子串了,所以可以利用以 str[j] 為中心回文子串長度即 arr[j]。在上圖的情況下,所以可以從箭頭所指出開始比較。還有一種情況:

lps01

這種情況下,不能直接利用以 str[j] 為中心回文子串長度即 arr[j],因為以 id 為中心回文子串長度只計算到了綠色箭頭所指之處,所以能力利用的信息是 mx-i,比較 mx-i 之后的字符。

下面個舉一例:

0123456789

ceabadabac

1112141?

當計算「?」即 arr[7] 的時候,id = 5,mx = 8,所以 arr[7] 可以給一個初值為 arr[2*id-7=3]=2,並且比較 str[7-2] 與 str[7+2] 是否相等......

0123456789

cdabadabac

1113141?

當計算「?」即 arr[7] 的時候,id = 5,mx = 8,此時 arr[7] 不能賦 arr[2*id-7=3]=3 的初值,因為以 id 為中心的回文子串只為圖中藍色部分:lps03 。所以,arr[7] 只能賦值為 mx-i = 8-7 = 1,繼續比較以更新 arr[7]。

Manacher 線性算法只要在紙上演算一遍就明白了。

從上面的描述,Manacher 算法只掃描了一遍,在具體計算中,借用了歷史數據以更快的速度算出當下的結果,從而避免重復的比較,因此是線性的?!(筆者還無法證明)

下面是 Python 的算法描述:

str = "abcdcba "
str = "#" + "#".join(str) + "#"
print str

i = 0
mx = 0
id = 0
p = [0 ] * len(str)
while i<len(str):
    if mx > i:
        p[i] = min(p[ 2*id-i],mx-i)
    else:
        p[i] = 1

    while i-p[i] >=0 and i+p[i] < len(str) and str[i-p[i]]==str[i+p[i]]:
        p[i] += 1

    if mx < p[i]+i:
        mx = p[i] + i
        id = i
    i+=1

print p

#a#b#c#d#c#b#a#

[1, 2, 1, 2, 1, 2, 1, 8, 1, 2, 1, 2, 1, 2, 1]

一個小 trick 是在原串中穿插字符「#」,可以將統一奇數回文串和偶數回文串的情況。

搗亂 2013-06-30

http://daoluan.net


免責聲明!

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



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