Manacher算法讲解——字符串最长回文子串

引 入 引入

Manachar算法主要是处理字符串中关于回文串的问题的,这没什么好说的。

M a n a c h e r 算 法 Manacher算法 Manacher

朴素

求一个字符串中以每个点为中心的最长回文子串,这是 m a n a c h e r manacher manacher 直接解决的问题。

以每个点为中心的回文子串是连续的,它并不像字符串的 b o r d e r border border ,因此可以二分,于是在不知道 m a n a c h e r manacher manacher 的情况下其实可以用字符串哈希+二分,只不过是 O ( n l o g n ) O(nlogn) O(nlogn) 的。

但是 m a n a c h e r manacher manacher 可以 O ( n ) O(n) O(n) 算出,因此在有些专门卡 l o g log log 的题目中 m a n a c h e r manacher manacher 有不可比拟的优越性。

我们不妨设以 i i i 为中心的最长回文子串长度为 F [ i ] ∗ 2 + 1 F[i]*2+1 F[i]2+1 m a n a c h e r manacher manacher 实际上是利用了一个回文字符串内的 F [ ] F[] F[] 的性质。

一个回文字符串,因为它是对称的,因此在仅考虑此字符串的情况下,它的 F F F 数组也是对称的,这便是 m a n a c h e r manacher manacher 算法的核心了吧。

因此,我们考虑从左到右依次计算 F [ i ] F[i] F[i] ,当遍历到一个点 i i i 时,首先 F [ i − 1 ] F[i-1] F[i1] 肯定已经算出来了。

如果 F [ i − 1 ] = = 0 F[i-1]==0 F[i1]==0 就直接暴力扩展 i ,否则,如果 F [ i − 2 ] < F [ i − 1 ] − 1 F[i-2] < F[i-1]-1 F[i2]<F[i1]1,说明什么呢?
在这里插入图片描述
F [ i ] = F [ i − 2 ] F[i]=F[i-2] F[i]=F[i2],一定是这样的.

  1. F [ i ] F[i] F[i] 肯定不能小于 F [ i − 2 ] F[i-2] F[i2] ,因为这个回文子串以 i − 1 i-1 i1 为对称中心,两边对称的两点,既然其中 i − 2 i-2 i2 可以扩大到 F [ i − 2 ] F[i-2] F[i2],且在 i − 1 i-1 i1 为中心的大回文串内 ,由于对称性,因此 [ i − F [ i − 2 ] , i + F [ i − 2 ] ] [i-F[i-2],i+F[i-2]] [iF[i2],i+F[i2]] 范围内也一定是个回文串。
  2. F [ i ] F[i] F[i] 也不能大于 F [ i − 2 ] F[i-2] F[i2] ,因为以每个点为中心的回文子串是连续的, F [ i ] > F [ i − 2 ] F[i]>F[i-2] F[i]>F[i2] 意味着 F [ i ] F[i] F[i] 可以为 F [ i − 2 ] + 1 F[i-2]+1 F[i2]+1,而由于 F [ i − 2 ] < F [ i − 1 ] − 1 F[i-2] < F[i-1]-1 F[i2]<F[i1]1 ,对于 i − 2 i-2 i2 来说, F [ i − 2 ] + 1 F[i-2]+1 F[i2]+1 仍然在大回文串范围内,因此 F [ i − 2 ] F[i-2] F[i2] 肯定就不止这个数了。

然后呢,是不是可以继续利用这个大红串呢?我们还可以考虑 F [ i − 3 ] F[i-3] F[i3] 是否小于 F [ i − 1 ] − 2 F[i-1]-2 F[i1]2 来决定 F [ i + 1 ] F[i+1] F[i+1] ,考虑 F [ i − 4 ] F[i-4] F[i4] 是否小于 F [ i − 1 ] − 3 F[i-1]-3 F[i1]3 来决定 F [ i + 2 ] F[i+2] F[i+2] ……直到超出红框或者:有一个 F [ i − 2 − x ] ≥ F [ i − 1 ] − 1 − x F[i-2-x] ≥ F[i-1]-1-x F[i2x]F[i1]1x,这时候, F [ i + x ] F[i+x] F[i+x] 至少能扩大到红框边界吧,因为它的对称点都能扩大到红框边界。然后,再把 F [ i + x ] F[i+x] F[i+x] 暴力往外扩。

我们会发现,整个过程中,每次往外扩一定能使前面所有的 i + F [ i ] i+F[i] i+F[i] 的最大值变大,因此,它是线性的。

转化

很明显,对于一个回文字符串而言,并不一定中心是一个字符,也有可能是两个相同的字符的中界,即回文串长度为偶数,因此,一般把原串中每两个字符中间添一个没出现过的字符,于是所有的 F [ i ] ∗ 2 + 1 F[i]*2+1 F[i]2+1(包括边界的)都会变成 ( F [ i ] ∗ 2 + 1 ) ∗ 2 + 1 (F[i]*2+1)*2+1 (F[i]2+1)2+1 ,这样就可以解决长度为偶数的情况。

模板

void Manacher(char *s,int *F,int n) {
    
    // 已是转化后的 S 
	F[1] = 0;
	for(int i = 2;i <= n;i ++) {
    
    
		int j = i;
		while(j < i-1+F[i-1] && j+F[i-1-(j-i+1)] < i-1+F[i-1]) F[j] = F[i-1-(j-i+1)],j ++;
		F[j] = max(0,i-1+F[i-1] - j);
		while(j+F[j] < n && j-F[j] > 1 && s[j+F[j]+1] == s[j-F[j]-1]) F[j] ++;
		i = j;
	}
	return ;
} 

猜你喜欢

转载自blog.csdn.net/weixin_43960414/article/details/111058295