前面用了动态规划法来求解了这个问题如何求字符串里的最长回文子串,虽然动态规划法总是能帮我们解决不少棘手的问题,但是在求解这个问题上,显然时间复杂度不尽如人意.所以,这一次来看一下其他的算法,意在解决动态规划法中时间复杂度过高的问题.首先来看一下这个中心扩展法.
先贴代码.再谈理论.
#中心扩展法求回文字符串问题
class GetPalindromeStr:
def __init__(self):
#保存最终的最大回文子串的起始索引和长度
self.startIndex = -1
self.lens = 0
def getStartIndex(self):
return self.startIndex
def getLens(self):
return self.lens
'''
对字符串strs,以c1, c2为中心向两边扩展,查找最大长度的回文字符串
'''
def expand(self, strs, c1, c2):
n = len(strs)
while c1 >= 0 and c2 < n and strs[c1] == strs[c2]:
c1 -= 1
c2 += 1
tmpStartIndex = c1 + 1
tmpLen = c2 - c1 - 1
if tmpLen > self.lens:
self.lens = tmpLen
self.startIndex = tmpStartIndex
'''
在整个字符串中通过遍历每个字符,再以每个字符为中心扩展以求得最大长度的回文字符串
'''
def getLongestPalindromeStr(self, strs):
if strs == None:
return
n = len(strs)
i = 0
while i < n-1:
self.expand(strs, i, i)
self.expand(strs, i, i+1)
i += 1
if __name__ == "__main__":
strs = "abcdefgfedaccdca"
gps = GetPalindromeStr()
gps.getLongestPalindromeStr(strs)
print("一开始的字符串为:" + strs)
lastIndex = gps.startIndex + gps.lens - 1
print("这个字符串的最大的回文子串为:" + strs[gps.startIndex:lastIndex+1])
中心扩展法的思想是我们对字符串的人任一个字符向两边扩展,或者以这个字符和它相邻的字符为中心向两边扩展.因为回文字符串根据字符串长度的奇偶性有两种情况,第一种:当长度为奇数时,它的中心字符只有一个;第二种:当长度为偶数时,它的中心字符是两个,而且这两个字符要相等.要考察一个字符串是否是回文字符串,我们是通过遍历这个字符串的所有字符,并且以这个字符为中心向两边扩展,通过比较两边字符是否相等来决定是否继续扩展.直到某两边的字符不相等或者其中一边的字符索引越界了,我们既要考虑长度为奇数的情况也要考虑长度为偶数的情况,最好的办法是我们先考虑以某个字符串 s i s_{i} si为中心扩展得到的最大回文字符串,再考虑以 s i s i + 1 s_{i}s_{i+1} sisi+1为中心的最大回文字符串.设计一个临时长度和临时的起始索引tmpLen,tmpStartIndex,在每一次做中心扩展以后,将长度和起始索引分别复制给这两个变量.同时设计一个保存当前最大的长度和起始索引这两个类属性,随时保存更新过后的长度和索引信息.也就是代码中的self.lens和self.startIndex.
做中心扩展的方法如下:
'''
对字符串strs,以c1, c2为中心向两边扩展,查找最大长度的回文字符串
'''
def expand(self, strs, c1, c2):
n = len(strs)
while c1 >= 0 and c2 < n and strs[c1] == strs[c2]:
c1 -= 1
c2 += 1
tmpStartIndex = c1 + 1
tmpLen = c2 - c1 - 1
if tmpLen > self.lens:
self.lens = tmpLen
self.startIndex = tmpStartIndex
其中c1, c2就是中心.如果要看奇数情况的中心扩展,就让c1 == c2,
如果要看偶数情况的中心扩展,就让c1 + 1 = c2.整个算法是面向对象的,即创建一个获得最大回文子串的类,设计相应的属性和方法.在主方法里只有创建这个类下的对象,才能对这个类的属性和方法进行调用.而遍历这个字符串的"中心"这个方法就更简单了.
'''
在整个字符串中通过遍历每个字符,再以每个字符为中心扩展以求得最大长度的回文字符串
'''
def getLongestPalindromeStr(self, strs):
if strs == None:
return
n = len(strs)
i = 0
while i < n-1:
self.expand(strs, i, i)
self.expand(strs, i, i+1)
i += 1
完整的代码及演示结果请翻到上面.
因为expand()方法的时间复杂度为O(N),而getLongestPalindrome()方法的时间复杂度为O(N).所以这个算法最坏情况的时间复杂度为O(N^2).但思想简单,相对于动态规划,它的expand()方法的循环是不太容易执行到O(N)的.也没有在二维数组中填值,空间复杂度为O(1).
那么,接下来一个更为天才的算法就将彻底解决这个时间复杂度问题.
Manacher
在介绍这个算法之前,有必要先引入一些概念,以方便后面代码的执行.
1.分割字符.
这是一种不属于原字符串的字符,一般可以用 ∗ * ∗来表示.它插在每两个字符之间,首尾也要插入,目的是把这个字符串的长度变为奇数个.这样就能把原来所谓奇数偶数的两个情况统一了.比如说字符串"acca",在原来如果考虑以c为中心的回文字符串,是只有"c"本身的,只有考虑以"cc"为中心才会得到更长的回文字符串.但我们插入了分割字符后,原来的字符串就变成了" ∗ a ∗ c ∗ c ∗ a ∗ *a*c*c*a* ∗a∗c∗