最近在解决最大回文子串系列问题中遇到了不少困扰,在查阅资料和敬读了各位csdn大佬的各类总结后,我想自己对于解决最大回文子串问题中的高效算法——马拉车manacher算法,进行自我总结,方便后续刷题需要,如果文章有其他问题或者雷同现象,请各位大佬及时批评指正。
目前解决最大回文子串有以下几种常见解法:
1.暴力法O(n3)
2.中心扩散法O(n2)
3.hash+二分法O(n2)
4.动态规划O(n2)
5.manacher算法O(n)
以上算法在有作者已经做出了初步介绍,大家可以去查看他的内容进行了解:
最长回文子串的五种求法(暴力、中点扩散、DP、hash+二分、Manacher)_求最长回文子串-CSDN博客
在这里我们重点介绍对时间优化最好的manacher算法O(n)
问题描述:
L2-008 最长对称子串
对给定的字符串,本题要求你输出最长对称子串的长度。例如,给定
Is PAT&TAP symmetric?
,最长对称子串为s PAT&TAP s
,于是你应该输出11。输入格式:
输入在一行中给出长度不超过1000的非空字符串。
输出格式:
在一行中输出最长对称子串的长度。
输入样例:
Is PAT&TAP symmetric?
输出样例:
11
manacher算法的核心思想在于在中心扩散法的基础上进行二次优化,使得算法在时间复杂度上取得进一步的突破,在常规的中心扩散法有两个弊端:
1.字符串的奇偶不确定,从任意一个位置扩散的方式需要分奇偶来讨论;
例如,当字符串长度为10时,我们在的扩散方法是向两边同时发散的:
a[4]->a[3]->a[2]->a[1]->a[0]
a[5]->a[6]->a[7]->a[8]->a[9]
例如,当字符串长度为11时,我们在的扩散方法是从一个点向外发散的:
a[5]->a[4]->a[3]->a[2]->a[1]->a[0]
a[5]->a[6]->a[7]->a[8]->a[9]->a[10]
2.存在大量重复性搜索工作,比如在我们从第1个字符出发,逐一搜寻,已经在第 i 处已经找到一个长度为 k 的回文字符串,此时说明我们根据回文字符串的对称性,很容易知道在第 i - n/2(这个长度我们已经通过遍历找到) 与第 i + n/2处(这是我们之后要搜索的位置)(n = 0,1,2,3.......k) 的 回文字符串是完全相等的,当然前提是 i + n/2处的回文字符串仍然包含在区间[ i - k/2, i + k/2 ]内,不然只能老老实实地逐一前后对照。这样考虑我们在理论上几乎可以将时间复杂度对半减少;
在这些文章中对这一优化有详细的图文解释:
Manacher's ALGORITHM: O(n)时间求字符串的最长回文子串 - Felix021 - So far so good
动态规划之马拉车算法(Python解法)_python manacher 算法-CSDN博客
下面我们讨论优化方法
优化1:首先将任意字符串的长度转化为奇数,具体方法为在字符串的中间及两端依次插入特殊符号,如‘#’,(还可以在字符串前端再加上一个其它符号,如‘$’,这样可以在读取到字符串头尾时自动结束,实现对约束条件的简化,这个我们放在后面讲),这样就轻易的实现了字符串长度的统一;
对字符串s的预处理代码:
s = '#' + '#'.join(self) + '#'
原理:奇数+偶数(#)+偶数(两个'#') 或者 偶数+奇数(#)+偶数(两个'#') = 奇数
在接下来介绍优化2之前我们需要先引入manacher算法必要的相关变量:
1 id 为已知的 {右边界最大} 的回文子串的中心,当然最开始我们也不知道 i 的位置,这是在逐一遍历时随遍历的进行发生动态变化的;
2 mx表示上面提到的{右边界最大} 的回文子串的右边界,用来检查优化2是否能实现,即某一点关于 i 点右侧的对称点是否在已知{右边界最大} 的回文子串的范围内;
3 列表P[i] :用来记录以字符S[i]为中心的最长回文子串向左/右扩张的长度;
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正好是原字符串中回文串的总长度)
初始化:
lens = len(s)
p = [0] * lens
mx = 0
id = 0
优化2:在遍历过程中分为两种情况;
1.对某一个位置的扩散刚开始时:
for i in range(lens):
.......
if mx <= i:
//遍历刚开始时,mx还没有逐步确定
p[i] = 1
while i-p[i] >= 0 and i+p[i] < lens and s[i-p[i]] == s[i+p[i]]:
//前两个是基本的越界检查(当在s前面另加符号‘$’后就不需要第一个了),后面是对是否回文的检查
p[i] += 1
2.是符合满足优化2 或者 是在上面提到的优化2无法实现时:
for i in range(lens):
.......
if mx > i:
p[i] = min(mx-i, p[int(2*id-i)])
//如果超过边界则不满足优化2,p[i] = mx-i(在离开区域前的最大回文长度);
如果满足优化2,根据对称性,p[i] = [int(2*id-i)]
while i-p[i] >= 0 and i+p[i] < lens and s[i-p[i]] == s[i+p[i]]:
p[i] += 1
然后再实现对mx与id的逐步更新
for i in range(lens):
.......
if(i+p[i]) > mx:
mx, id = i+p[i], i
综上,我们对以上操作进行封装,得到manacher函数
def manacher(self):
s = '#' + '#'.join(self) + '#'
lens = len(s)
p = [0] * lens
mx = 0
id = 0
for i in range(lens):
if mx > i:
p[i] = min(mx-i, p[int(2*id-i)])
else :
p[i] = 1
while i-p[i] >= 0 and i+p[i] < lens and s[i-p[i]] == s[i+p[i]]:
p[i] += 1
if(i+p[i]) > mx:
mx, id = i+p[i], i
i_res = p.index(max(p))
s_res = s[i_res-(p[i_res]-1):i_res+p[i_res]]
return max(p)-1
检验正确性,发现其完全正确,借助manacher我们还可以解决类似的如输出某字符串包含的所有回文子串等一系列相关问题
print(manacher(''))
print(manacher(' '))
print(manacher('abab'))
print(manacher('aaaaaaa'))
print(manacher('Is PAT&TAP symmetric?'))