神奇的算法–manacher算法
在获取一个字符串中最长的回文串,一般的做法是遍历每一个字符,然后以其为中心向两边展开,最终通过比较所有的情况后得到最长的字符串
这方法是大部分人能够想到的。
但是在时间复杂度上,是O(n²)。如,对于长度为1000的字符串,计算量将会达到1百万。
这时候,如果你用manacher算法,其时间复杂度是惊人的O(n),即计算量和字符串长度是一个数量级!可惜的是,manacher算法仅仅适用于解决回文串。
(自己写了好几次解释,但是最后看来还是官方的解答最清晰,所以选择转载官方的)
为了表述方便,我们定义一个新概念臂长
,表示中心扩展算法向外扩展的长度。如果一个位置的最大回文字符串长度为2 * length + 1
,其臂长为length
。
下面的讨论只涉及长度为奇数的回文字符串。长度为偶数的回文字符串我们将会在最后与长度为奇数的情况统一起来。
在中心扩展算法的过程中,我们能够得出每个位置的臂长。那么当我们要得出以下一个位置 i 的臂长时,能不能利用之前得到的信息呢?
答案是肯定的。具体来说,如果位置 j 的臂长为 length,并且有j + length > i
,如下图所示:
当在位置 i 开始进行中心拓展时,我们可以先找到 i 关于 j 的对称点2 * j - i
。那么如果点 2 * j - i 的臂长等于 n,我们就可以知道,点 i 的臂长至少为min(j + length - i, n)
。那么我们就可以直接跳过i
到i + min(j + length - i, n)
这部分,从i + min(j + length - i, n) + 1
开始拓展。
我们只需要在中心扩展法的过程中记录右臂在最右边的回文字符串,将其中心作为 j,在计算过程中就能最大限度地避免重复计算。
那么现在还有一个问题:如何处理长度为偶数的回文字符串呢?
我们可以通过一个特别的操作将奇偶数的情况统一起来:我们向字符串的头尾以及每两个字符中间添加一个特殊字符 #,比如字符串 aaba 处理后会变成 #a#a#b#a#。那么原先长度为偶数的回文字符串 aa 会变成长度为奇数的回文字符串 #a#a#,而长度为奇数的回文字符串 aba 会变成长度仍然为奇数的回文字符串 #a#b#a#,我们就不需要再考虑长度为偶数的回文字符串了。
注意这里的特殊字符不需要是没有出现过的字母,我们可以使用任何一个字符来作为这个特殊字符。这是因为,当我们只考虑长度为奇数的回文字符串时,每次我们比较的两个字符奇偶性一定是相同的,所以原来字符串中的字符不会与插入的特殊字符互相比较,不会因此产生问题。