manacher算法 主要用来解决最长回文子串
研究了两天这个算法理论,终于搞清楚了,打算自己写详细一点,方便大家阅读,也方便自己以后复习看起来容易回忆。
参考了很多文献和他人写的代码,也看了一个视频,发现如果真想自己搞懂,必须亲自拿笔走一遍过程
这篇文章写的特别详细:最长回文字符串
参考
马拉车求最长回文子串
Manacher算法
最长回文串四种解法
python实现最长回文串
我自己的总结如下:
首先用一个非常巧妙的方式,将所有可能的奇数/偶数长度的回文子串都转换成了奇数长度:在每个字符的两边都插入一个特殊的符号。比如 abba 变成 #a#b#b#a#, aba变成 #a#b#a#。 为了进一步减少编码的复杂度,可以在字符串的开始加入另一个特殊字符,这样就不用特殊处理越界问题,比如$#a#b#a#。
下面以字符串12212321为例,经过上一步,变成了 S[] = “$#1#2#2#1#2#3#2#1#”;
然后用一个数组 P[i] 来记录以字符S[i]为中心的最长回文子串向左/右扩张的长度(包括S[i],也就是把该回文串“对折”以后的长度),比如S和P的对应关系:
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正好是原字符串中回文串的总长度)
注意:
有奇数回文串和偶数回文串,例如:aba,abba
加入特殊符号#以后,如何回文中心是#,则以前的回文串是偶数回文,如果回文中心是字母,则以前的回文串是奇数回文。
为什么用马拉车算法?因为加入特殊符号之后找中心点容易。
先附上代码,然后一步步讲解
def manacher(s):
#预处理
s='#'+'#'.join(s)+'#'
#创建一个全是0的列表,用来存储以每个字符为中心点的回文长度的半径
RL=[0]*len(s)
#回文中心到右边最远的距离,即回文中心加上半径的距离-1
MaxRight=0
#回文中心的index
pos=0
#原最长的回文串的长度
MaxLen=0
for i in range(len(s)):
if i<MaxRight:
RL[i]=min(RL[2*pos-i], MaxRight-i)
else:
RL[i]=1
#尝试扩展,注意处理边界
while i-RL[i]>=0 and i+RL[i]<len(s) and s[i-RL[i]]==s[i+RL[i]]:
RL[i]+=1
#更新MaxRight,pos
if RL[i]+i-1>MaxRight:
MaxRight=RL[i]+i-1
pos=i
#更新最长回文串的长度
MaxLen=max(MaxLen, RL[i])
print(RL)
return MaxLen-1
if __name__ == '__main__':
s = "babbad"
# s1= 'abbbfdfbbssss'
print(manacher(s))
s = b a b b a d
加入特殊符号之后:
s = # b # a # b # b # a # d #
index = 0 1 2 3 4 5 6 7 8 9 10 11 12
MaxRight=0 #回文中心的index
pos=0 #原最长的回文串的长度
MaxLen=0