问题形式
给定一个字符串,求该字符串的最长回文子串。
一个简单的暴力实现
由于回文串是对称的,我们枚举对称轴(注意奇偶分类讨论)。然后从该对称轴向左向右扩展,直到两字母不相等。此时的字符串长度一定是以当前对称轴作为中心的最长回文子串。
时间复杂度。
Manacher - 暴力的优化
为了避免奇偶分类讨论,我们现在字符之间以及字符串两端插入一个特殊字符(与字符串中所有字符都不相等)。
例如:ababaab -> #a#b#a#b#a#a#b#
,显然,原来是回文串的还是回文串,原来不是回文串的仍然不是回文串,长度稍做处理即可。
通过观察发现,暴力主要慢在相同的状态被重复枚举,没有利用回文串的性质。
记表示以为对称轴时,最长的回文串的右端距离(包括最右端的字符和)的长度为。栗子:
1 | char: # a # b # a # |
如何快速求出?
考虑根据回文串的性质,利用已经求出的值来求出当前的。
引入两个辅助变量。表示当前访问到的所有回文子串,所能触及的最右一个字符的位置,表示对应的对称轴。
为方便观察,下面的图示中记为关于对称的位置,表示,表示,表示未触及的位置。
1 | | | | | |x| | | | | |m| | | | | |r|*|*|*| |
当前要求的位置一定在右边。那么我们根据和讨论:
当时
1 | | | | | |x| | |j| | |m| | |i| | |r|*|*|*| |
记为关于的对称点。由于已知,我们尝试利用得出的下界。
又分成两种小情况:
比较小,向左没有超过
大概情况如图所示:
1 | | | | | |x| |-|j|-| |m| |-|i|-| |r|*|*|*| |
此时显然的下界为。如图所示的情况一定也是上界,但如果刚好等于,那么有可能可以继续扩展。由于不影响复杂度,为方便起见,无论哪一种情况都尝试扩展。
比较大,向左超过
大概情况如图所示:
1 | | | |
此时暂时只能确定两根较长的线之间的部分时回文的,所以的下界是。然后同样地直接继续扩展即可。
实际实现中,这两种情况不需要特殊判断,只要在和中取即可。
当时
此时显然的求值不能利用之前的任意值,所以定下界为,也直接扩展即可。
总结
步骤
综上所述,从左到右枚举的过程中,对于每个,算法步骤如下:
- 若,则令,否则令(显然,关于的对称点为)。
- 以为中心扩展回文串,直到左右两边字符不同,或者到达边界。
- 更新和。
- 更新答案。扩展出来的子串在原串中的长度一定为。因为在添加字符后的串中该子串长度为,由于首尾一定是特殊字符,所以特殊字符数量比其他字符多,所以特殊字符数量为,其他字符为。
一个小技巧
如果你的字符串下标从开始,你可以令为那个特殊字符,这样就省去了判断边界的问题。
复杂度分析
根据上面三种情况的分析,在继续扩展时,每一次的扩展都可以更新,而最多变化次,所以总的时间复杂度为的。
代码实现
1 |
|