求解最長迴文子串必用算法:Manacher 算法。
這裏不解釋啥是迴文子串了,直接總結下算法思路。
第一步:將原字符串首尾以及字符串之間添加'#'字符,目的是原字符串迴文子串的中心點可能有兩種,奇數長度和偶數長度。例如aba和abba,正常求解需要分情況討論,所以在字符串之間加上沒出現過的字符例如'#',那麼都會變成奇數長度,不用分情況討論。#a#b#a#(7)#a#b#b#a#(9),'#'長度永遠比字符長度大1。用S指代構建的字符串
第二步,構建與S長度相同的數組P,P的每一位存儲字符串中對應該位的最大回文長度半徑。
第三步,初始化P以及最大回文的中心位置C以及最右邊位置R。R是當前最大回文子串最右邊的座標,馬拉車算法的精髓就是利用了迴文串的對稱性,所以在更新迴文長度數組P的時候,要區別當前位置i是在迴文子串內還是不在,至於兩種情況有何不同看下面。
第一步沒問題,第二步初始化過程。
例如字符串fabccbad,經過第一步得到
#f#a#b#c#c#b#a#d#.
初始化P[0]=0,P[1]=1,因爲P[0]是開頭,P[1]第一個字符最大回文肯定是它自己,長度爲1。此時中心點位置就是1,C=1。R=i+P[i],當前是R=1+1=2
此時從下標2開始循環,
首先區別當前下標i是否在R內:
if i>R:直接使用中心擴展法,也就是以i爲中心點,用前後指針往前後遍歷找最長迴文子串,然後用新的R和C來更新原來的R,C。
if i<=R:i在R內,先得到i對於當前迴文串中心點C的鏡像對應點i_mir=2*C-i。P[i]=P[i_mir]。但是這裏注意邊界情況。
第一種,i_mir如果正好在左邊界處,也就是當前字符的鏡像字符正好是字符串的頭部,那P[i]不應該等於1,因爲這個1是因爲邊界,我們人爲初始化出來的,此時P[i]仍然需要用中心擴展法來確定P[i],並且用更大的R來更新R。
第二種,i+P[i_mir]>R。也就是當前座標i雖然在迴文串內,但是i+P[i_mir],它的右邊界越過了R,此時P[i]只能等於R-i,接着在這基礎上用中心擴展求長度,此時指針應該基於半徑R-i的基礎上遍歷。
如果沒有邊界問題,P[i]=P[i_mir]。
所以對於
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
# f # a # b # c # c # b # a # d #
P: 0 1 0 1 0 1 0 1 6 1 0 1 0 1 0 1 0
在c#c中間的#時,R,C更新爲14(i+P[i]),8(i),後面的字符都在R內,且沒有邊界問題,直接更新。
算法看着和中心擴展類似,但是每次我們的i超出R的時候,總會更新新的R,所以元素最多遍歷兩次,(更新新的R時一次,出現邊界問題使用中心擴展時一次)。所以算法複雜度仍然是線性O(N)。
在最後得到的P數組裏,找到元素值最大的下標i。(i-P[i])/2就是原字符串迴文子串的開始下標,P[i]就是長度。
貼個python代碼:
s='#b#b#'
def malache(s):
P=[0]*len(s)
P[1]=1
C=1
R=C+P[C]
for i in range(2,len(s)):
if i==R+1 or 2*C-i<=2:
P[i]=0
t1=i-1
t2=i+1
while t1>=0 and t2<len(s) and s[t1]==s[t2]:
P[i]+=1
t1-=1
t2+=1
elif i+P[2*C-i]>=R:
P[i]=R-i
t1=i-P[i]-1
t2=i+P[i]+1
while t1>=0 and t2<len(s) and s[t1]==s[t2]:
P[i]+=1
t1-=1
t2+=1
else:
P[i]=P[2*C-i]
if i+P[i]>R:
R=i+P[i]
C=i
return P
P=malache(t)
index=P.index(max(P))
start=(index-P[index])//2
end=start+P[index]
return s[start:end]