Manacher Algorithm
解决了什么问题?
寻找一个字符串的最大回文子串。
给定一个字符串,返回该字符串最大的回文子串以及该回文子串的起始位置。
目标待求量:
- 回文子串的长度
- 回文子串的起始位置
看到这个题目,我们心里可能会比较迷惑,找最长的回文子串?通过暴力枚举法找到一个字符串的所有的子串,然后对他们依次判断是否为回文串。但是这样的暴力法得到的最终的时间复杂度可能会是 O ( n 2 ) O(n^2) O(n2) ,为了找到一个耗时较少的方案于是就有了以下的一个算法——
一、字符串的预处理
对于一个字符串来说,通常他的长度可能为奇数,也可能为偶数。而且对于这两种情况的讨论使得情况变得复杂起来,于是manacher首先解决的就是将这两种情况合二为一讨论,是通过怎样的处理呢?
没错就是全部变成奇数 —— 通过在原始字符串中插入一个特殊字符(以下的例子中使用的是‘#’)对原始的字符串进行预处理。其中的转换过程如下图所示——
根据以上的预处理,我们可以将一个原始长度为n的字符串预处理为一个长度为2n+1的字符串,其中穿插着的是特殊字符。而且不难得知最终的得到的字符串肯定是一个长度为奇数的字符串。
而且以上的这一步操作实际上将原始string中的下标和现在的下标进行了以下的映射——
二、计算回文半径
我们首先看看计算回文半径是有什么作用,计算出来回文半径和原始字符串的回文长度和起始位置之间的关系又是怎样的呢?如下图,我们这样定义回文半径——
在上图中,假设以位置3所在的点为对称轴,位置3所在的点则被称为圆心。根据上图的信息,位置2和位置4关于对称轴对称;位置1和位置5关于对称轴对称;所以位置3的对称半径就是从位置1到位置3的距离,即为3。实际上这里所说的回文和数学上的对称是一样的概念,因此此处的对称半径我们称它为回文半径。(以上概念全是我自己随便取的)
有了以上的理论基础之后,我们来看看这个回文半径和原始的回文串长度有什么样的对应关系,我们这边先给出上述例子中的所有会问半径数组(稍后我们呢探究怎样计算这个回文半径数组),如下图
观察上图的局部如下图——
上图中使用红框中选中的部分就是预处理之后的一个回文子串,其中Circle对应的是圆心,A表示左回文半径点,B表示右回文半径点。从A到B之间的序列组成一个回文串。在说明这个回文串和原始回文串之间的关系之前,我们需要说清楚几个问题——
1. 左右回文半径点一定落在“#”处
这里所说的回文半径点分别就是上图描述中的A和B,他们的落点一定是#
。
这里证明一下:实际上如果左右回文半径点的落点不是#
,也就是说他们的落点在某一个字符串上,那么一定存在A左边的一个点为#
,且B右边的一个点为#
。所以最终还是落在#
上。
根据这个结论以及预处理中的填充规律,#
的数量比字符串的数量多1
。如果假设原始字符串长度为n
,那么#
的长度为n+1
, 从A到B的字符串长度为2n+1
。—— 这里我们就构建了AB字符串长度和原始字符串长度的关系。
换个角度,我们从回文半径的角度看和AB字符串长度的关系。根据我们之前所说的半径定义,从A到Circle是一个半径r
,从Circle到B也是一个半径r
,那么AB字符串的长度为2r - 1
,减一是因为两个半径中都计算了Circle这个点,因此计算长度时将其剔除。所以这里我们从另外一个角度构建了AB字符串长度和半径之间的关系。
l e n A B = 2 n + 1 = 2 r − 1 ⇒ n = r − 1 len_{AB} = 2n + 1 = 2r - 1 \Rightarrow n = r - 1 lenAB=