抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >

问题形式

给定一个字符串,求该字符串的最长回文子串。

洛谷传送门

一个简单的暴力实现

由于回文串是对称的,我们枚举对称轴(注意奇偶分类讨论)。然后从该对称轴向左向右扩展,直到两字母不相等。此时的字符串长度一定是以当前对称轴作为中心的最长回文子串。

时间复杂度O(n2)\mathcal O(n^2)

Manacher - 暴力的优化

为了避免奇偶分类讨论,我们现在字符之间以及字符串两端插入一个特殊字符(与字符串中所有字符都不相等)。

例如:ababaab -> #a#b#a#b#a#a#b#,显然,原来是回文串的还是回文串,原来不是回文串的仍然不是回文串,长度稍做处理即可。

通过观察发现,暴力主要慢在相同的状态被重复枚举,没有利用回文串的性质。

hwihw_i表示以ii为对称轴时,最长的回文串的右端距离ii(包括最右端的字符和ii)的长度为hwihw_i。栗子:

1
2
3
4
5
char:    # a # b # a #
hw : 1 2 1 4 1 2 1

char: # a # b # b # a #
hw : 1 2 1 2 5 2 1 2 1

如何快速求出hwihw_i

考虑根据回文串的性质,利用已经求出的hwhw值来求出当前的hwihw_i

引入两个辅助变量MaxRight,MidMaxRight,MidMaxRightMaxRight表示当前访问到的所有回文子串,所能触及的最右一个字符的位置,MidMid表示对应的对称轴。

为方便观察,下面的图示中记xxMaxRightMaxRight关于MidMid对称的位置,rr表示MaxRightMaxRightmm表示MidMid*表示未触及的位置。

1
| | | | |x| | | | | |m| | | | | |r|*|*|*|

当前要求hwihw_i的位置ii一定在MidMid右边。那么我们根据iMaxRighti\le MaxRighti>MaxRighti>MaxRight讨论:

iMaxRighti\le MaxRight

1
| | | | |x| | |j| | |m| | |i| | |r|*|*|*|

jjii关于mm的对称点。由于hwjhw_j已知,我们尝试利用hwjhw_j得出hwihw_i的下界。

又分成两种小情况:

hwjhw_j比较小,向左没有超过xx

大概情况如图所示:

1
| | | | |x| |-|j|-| |m| |-|i|-| |r|*|*|*|

此时显然hwihw_i的下界为hwjhw_j。如图所示的情况一定也是上界,但如果i+hwi1i+hw_i-1刚好等于MaxRightMaxRight,那么有可能可以继续扩展。由于不影响复杂度,为方便起见,无论哪一种情况都尝试扩展。

hwjhw_j比较大,向左超过xx

大概情况如图所示:

1
2
3
4
5
                    |             |
/-------j----|--\ |
| | | | |x| | |j| | |m| | |i| | |r|*|*|*|
\|------i------|/
| |

此时暂时只能确定两根较长的线之间的部分时回文的,所以hwihw_i的下界是MaxRighti+1MaxRight-i+1。然后同样地直接继续扩展即可。

实际实现中,这两种情况不需要特殊判断,只要在hwjhw_jMaxRighti+1MaxRight-i+1中取minmin即可。

i>MaxRighti>MaxRight

此时显然hwihw_i的求值不能利用之前的任意值,所以定下界为11,也直接扩展即可。

总结

步骤

综上所述,从左到右枚举ii的过程中,对于每个ii,算法步骤如下:

  1. iMaxRighti\le MaxRight,则令hwi=min(hw2Midi,MaxRighti+1)hw_i=min(hw_{2*Mid-i}, MaxRight-i+1),否则令hwi=1hw_i=1(显然,ii关于MidMid的对称点jj2Midi2*Mid-i)。
  2. ii为中心扩展回文串,直到左右两边字符不同,或者到达边界。
  3. 更新MaxRightMaxRightMidMid
  4. 更新答案。扩展出来的子串在原串中的长度一定为hwi1hw_i-1。因为在添加字符后的串中该子串长度为2hwi12hw_i-1,由于首尾一定是特殊字符,所以特殊字符数量比其他字符多11,所以特殊字符数量为hwihw_i,其他字符为hwi1hw_i-1

一个小技巧

如果你的字符串下标从11开始,你可以令s0s_0为那个特殊字符,这样就省去了判断边界的问题。

复杂度分析

根据上面三种情况的分析,在继续扩展hwihw_i时,每一次的扩展都可以更新MaxRightMaxRight,而MaxRightMaxRight最多变化nn次,所以总的时间复杂度为O(n)\mathcal O(n)的。

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <cstdio>
#include <algorithm>
char buf[11000005], *ps = buf;
int n, hw[22000005], mr = 0, mid = 0, ans = 0;
char s[22000005];
int main(){
buf[fread(buf, 1, 11000005, stdin)] = '\0', s[0] = '#'; // 小技巧
while (*ps >= 'a' && *ps <= 'z') s[++n] = '#', s[++n] = *ps, ++ps;
s[++n] = '#'; // 插入特殊字符
for (register int i = 1; i <= n; ++i){
hw[i] = i <= mr ? std :: min(hw[(mid << 1) - i], mr - i + 1) : 1; // 得出下界
while (s[i - hw[i]] == s[i + hw[i]]) ++hw[i]; // 继续扩展
if (i + hw[i] - 1 > mr) mid = i, mr = i + hw[i] - 1; // 更新mr,mid
ans = std :: max(ans, hw[i] - 1); // 更新答案
}
printf("%d\n", ans);
}

参考资料

最长回文子串——Manacher 算法

评论