Python实现Manacher算法:从暴力枚举到线性时间复杂度
本文深入解析Python中求最长回文子串的算法演进。从时间复杂度为O(N^2)的暴力枚举出发,介绍中心扩展法的优化思路,最终重点讲解Manacher算法。该算法利用回文对称性将时间复杂度优化至O(N),并通过1000字符纯'a'字符串的实测数据,展示Manacher算法在耗时上相比暴力法(49.7s)和中心扩展法(0.33s)的显著优势,耗时仅0.008s。
给定一个字符串,任务是在其中寻找符合回文定义的最长子串。回文串是指正读和反读都相同的字符串,例如“aba”、“ababa”或“abba”。需要注意的是,单个字符以及两个相邻的相同字符也满足回文性质。
面对这一问题,最直观的解法是暴力枚举。该方法通过遍历字符串所有可能的子串起点,逐一检查子串是否满足回文条件,并记录最大长度。然而,这种算法的时间复杂度极高,最坏情况下达到O(N^2)。因此,一种优化方案是枚举子串的中心而非起点,从中心向两侧同时扩展以判断回文性。这种优化在字符串字符高度重复(如全为同一字符)的最坏场景下,能显著提升运行效率。
基于上述中心枚举的思路,虽然效率优于暴力法,但仍非最优。由于中心枚举同时涉及中心两侧的字符,我们可以利用对称性:若已知以左侧某点j为中心的最长回文子串长度,可据此推断以右侧对应点i为中心的回文性质。这一核心思想构成了Manacher算法的基础。
Manacher算法的设计十分精妙。假设当前遍历到的位置为i,若存在之前的位置j(j<i)已计算出其最长回文子串长度f[j],则该位置的影响范围覆盖为[j-f[j]/2, j+f[j]/2]。为了使左侧字符j对右侧当前点i的影响最大化,我们需要找到使右边界j+f[j]/2最大的那个j。一旦确定该j,若当前点i位于区间[j, j+f[j]/2]内,则需分两种情况讨论:
第一种情况是:i关于j的对称点i'的影响范围完全包含在j的影响范围内。根据回文串的对称性质,i的影响范围至少等于i'的影响范围,即f[i] >= f[i']。
第二种情况是:i关于j的对称点i'的影响范围未完全包含在j的影响范围内。此时,i右侧的有效影响范围至少延伸至[j-f[j]/2, i'],即满足不等式i+f[i]/2 >= i'-j+f[j]/2。
利用对称性关系i+i' = 2*j,可以推导出:在第一种情况下,f[i] >= f[2*j-i];在第二种情况下,f[i] >= f[j]+2*j-2*i。
综合上述两种情况,可得f[i] >= min(f[2*j-i], f[j]+2*j-2*i)。由于i右侧可能存在尚未遍历的字符,因此在此下限基础上,算法继续向两侧扩展,直到确定最长的回文子串。特别注意,当i右侧存在未处理字符时,初始值至少为1,需在此基础上继续扩展。
若当前点i位于j+f[j]/2之后,说明i未受到之前已计算字符的影响,此时只能从i开始逐一向两侧扩展判断。
该算法的优势在于只需遍历字符串一次,且扩展次数有限,因此整体时间复杂度可优化至O(N)。
以下是Python 3的实现代码。为了验证算法效率,代码中同时保留了最初的暴力枚举算法作为参照,用于对比最坏情况下的性能差异。
Python代码实现如下:
#求最长回文串类
class LPS:
#初始化,需要提供一个字符串
def __init__(self,string):
self.string = string
self.lens = len(self.string)
#暴力枚举:作为算法效率参照
def brute_force(self):
maxcount = 0
for j in range(self.lens):
for k in range(j,self.lens):
count = 0
l,m = j,k
while m>=l:
if self.string[l]==self.string[m]:
l,m = l+1,m-1
else:
break
if m<l:
count = k-j+1
if count>maxcount :
maxcount = count
return maxcount
#优化版:枚举子串中心
def brute_force_opti(self):
maxcount = 0
if self.lens == 1: #只有一个字符直接返回1
return 1
for j in range(self.lens-1): #枚举中心
count,u = 1,j
#对于奇数子串,直接扩展
for k in range(1,j+1): #两边扩展
l,m = u+k,j-k
if (m>=0)&(l<self.lens):
if(self.string[l]==self.string[m]):
count += 2
else:
break
if count>maxcount : #更新回文子串最长长度
maxcount = count
if self.string[j]==self.string[j+1]: #处理偶数子串,将两个相邻相同元素作为整体
u,count= j+1,2
for k in range(1,j+1): #两边扩展
l,m = u+k,j-k
if (m>=0)&(l<self.lens):
if(self.string[l]==self.string[m]):
count += 2
else:
break
if count>maxcount : #更新回文子串最长长度
maxcount = count
return maxcount
#manacher算法
def manacher(self):
s = '#'+'#'.join(self.string)+'#' #字符串处理,用特殊字符隔离字符串,方便处理偶数子串
lens = len(s)
f = [] #辅助列表:f[i]表示i作中心的最长回文子串的长度
maxj = 0 #记录对i右边影响最大的字符位置j
maxl = 0 #记录j影响范围的右边界
maxd = 0 #记录最长的回文子串长度
for i in range(lens): #遍历字符串
if maxl>i:
count = min(maxl-i,int(f[2*maxj-i]/2)+1)#这里为了方便后续计算使用count,其表示当前字符到其影响范围的右边界的距离
else :
count = 1
while i-count>=0 and i+count<lens and s[i-count]==s[i+count]:#两边扩展
count +=1
if(i-1+count)>maxl: #更新影响范围最大的字符j及其右边界
maxl,maxj = i-1+count,i
f.append(count*2-1)
maxd = max(maxd,f[i]) #更新回文子串最长长度
return int((maxd+1)/2)-1 #去除特殊字符
使用长度为1000的纯'a'字符串作为测试样例,对三种算法进行性能测试,结果如下:
暴力枚举耗时:49.719844秒
中心扩展法耗时:0.334019秒
Manacher算法耗时:0.008000秒
测试数据显示,当字符串长度为1000时,暴力枚举的耗时已难以接受。相比之下,中心扩展法效率有大幅提升,而Manacher算法耗时最短,展现了其在大规模数据处理上的显著优势。
本文介绍了Python中求解最长回文子串的算法优化过程,希望能为读者提供学习参考,欢迎继续支持本站获取更多技术内容。
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













