视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
python最长回文串算法
2020-11-27 14:21:34 责编:小OO
文档

这篇文章主要为大家详细介绍了python最长回文串算法的实践,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

给定一个字符串,要求在这个字符串中找到符合回文性质的最长子串。所谓回文性是指诸如 “aba”,"ababa","abba"这类的字符串,当然单个字符以及两个相邻相同字符也满足回文性质。

看到这个问题,最先想到的解决方法自然是暴力枚举,通过枚举字符串所有字串的起点,逐一判断满足回文性的子串,记录长度并更新最长长度。显然这种算法的时间复杂度是很高的,最坏情况可以达到O(N*N)。所以呢,这里提出一个优化的方案,通过枚举字符串子串的中心而不是起点,向两边同时扩散,依然是逐一判断子串的回文性。这种优化算法比之前的算法在最坏的情况下(即只有一种字符的字符串)效率会有很大程度的上升。

由上述的优化方案,我们知道了枚举中心要比枚举起点效率要好,然而这并不是最优的算法。由于枚举中心的算法同时影响的是中心两边的字符,所以我们可以通过枚举中心的左边字符作为中心的子串的回文性判断枚举中心右边的字符作为中心得子串的回文性,这就是manacher算法。

manacher算法思想非常巧妙,首先遍历字符串,假设 i 为枚举中心,则 j (j<i) 为中心的最长回文子串长度发f[j] 便已经求出,此时 j 的影响范围便是[j-f[j]/2,j+f [j]] 。为了使左边的字符 j 对枚举中心右边的影响最大,需要使 j+f[j]/2 最大。找到满足j+f[j]/2最大的 j 之后,若 i 在[j,j+f[j]/2]中,则分两种情况:

1 . i 关于 j 对称的字符i'的影响范围完全包含在j的影响范围内,则由于回文性,i 的影响范围大于等于i'的影响范围,即f[i]>=f[i']

2. 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。

综上1,2,可得f[i]>=min(f[2*j-i],f[j]+2*j-2*i)。由于i右边存在未遍历的字符,因此在此基础上,继续向两边扩展,直到找到最长的回文子串。

若i依然在j+f[j]/2后面,则表示i没有被前面的字符的影响,只能逐一的向两边扩展。

这个算法由于只需遍历一遍字符串,扩展的次数也是有限的,所以时间复杂度可以达到O(N)。

下面是Pthon3的程序,为了检测算法的效率,依然提供最初的暴力枚举算法作为最坏算法的参照。

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.719844s

中心枚举:0.334019s

manacher:0.008000s

由此可见,长度为1000时,暴力枚举的耗时已经无法忍受了,而相比而言,中心枚举在效率上已经有很大幅度的提升,最优的manacher耗时则为更短。

下载本文
显示全文
专题