題目
給定一個字符串 s,找到 s 中最長的迴文子串。你可以假設 s 的最大長度爲 1000。
思路
本文一眼看上去可以用動態規劃,解法詳見下一篇博客。
動態規劃複雜度是 ,看完題解後發現有更簡單的解法。
Manacher算法的複雜度可以到 。
本文基本參考這篇的講解,附帶自己的理解
Manacher 算法
原理
Manacher的是從暴力搜索簡化來的。查找最長迴文串最暴力的解法就是從一個字符向兩邊擴張知道不相等就查下一個,依次遍歷完字符串就可以了。
Manacher的原理和KMP類似,也是用一個輔助數組來省略回退。
本文的圖都是從這個連接裏截的,不想重新畫了
算法過程
Manacher還會在原數組中均勻插入一些字符,避免在找回文中點的時候指到兩個字符中間。
上圖,P中記錄的是迴文半徑,即T字符串中一個迴文的長度的一半。很好理解我們最後就是要構建這麼一個數組P,然後找出最大值。
這裏需要幾個變量來記錄一下過程,具體過程還是看這個連接,這裏只是記錄下自己的理解。
C
是當前最大回文串的中心位置,R
是當前最大回文串的右邊界。i
是要當前計算的字符。i_mirror
是i
相對於C
的鏡像位置。因爲是對稱的,P[i]
可以根據P[i_mirror]
計算出來。
當P[i] + i
,超過R
的時候,更新C
和R
到i
和P[i]+i
。
每次P[i]
擴張時超過R
,C
就會跳到i
。每個元素只遍歷兩次,複雜度 。
代碼
func longestPalindrome(s string) string {
var T string = preProcess(s);
n := len(T)
var P [2002]int;
C := 0
R := 0
var i int = 0
for i = 1; i < n - 1; i++ {
var i_mirror = 2 * C - i
if (R > i) {
P[i] = R - i // 防止超出 R
if (R - i > P[i_mirror]) {
P[i] = P[i_mirror]
}
} else {
P[i] = 0// 等於 R 的情況
}
// 碰到之前講的三種情況時候,需要利用中心擴展法
for ;T[(i + 1 + P[i])] == T[(i - 1 - P[i])]; {
P[i]++
}
// 判斷是否需要更新 R
if (i + P[i] > R) {
C = i
R = i + P[i]
}
}
// 找出 P 的最大值
maxLen := 0
centerIndex := 0
for i = 1; i < n - 1; i++ {
if (P[i] > maxLen) {
maxLen = P[i]
centerIndex = i
}
}
start := (centerIndex - maxLen) / 2 //最開始講的求原字符串下標
return s[start:start + maxLen]
}
func preProcess(ts string) string {
n := len(ts)
if (n == 0) {
return "^$"
}
ret := "^"
for i := 0; i < n; i++ {
ret = ret + " " + string(ts[i])
}
ret += " $"
return ret
}