Manacher 字符串回文查询算法说解

来源:互联网 发布:淘宝的老板是谁 编辑:程序博客网 时间:2024/05/16 17:26

Manacher算法是一种线性时间复杂度的字符串回文查询算法,可以查询给定字符串内所有的回文串。时间复杂度 O(n),空间复杂度 O(n),其中 n 为字符串的长度。

对于回文串而言,存在长度为奇数的回文串和长度为偶数的回文串,而增加了编程复杂度。而我们可以将字符串中每相邻的两个字符间增加一个特殊字符来解决这个问题。这样,字符串的长度变为 2 * n + 1 且所有的回文串的长度都为奇数,我们可以依次对每个字符搜索以其为中点的回文串。

举个例子:当字符串为 “ababcbaabc” 时,我们通过把它处理成 “#a#b#a#b#c#b#a#a#b#c#” (在这里假定特殊字符为 ‘#’,当然只要满足不可能在原字符串中出现的字符都可以作为特殊字符,且一个字符串中只能出现一种特殊字符)来解决字符串长度奇偶性的问题。这样原字符串中长度为偶数的回文串转变为以特殊字符 ‘#’ 为中点的长度为奇数的回文串。

为了本文叙述的方便起见,记原字符串为 str[0 .. n - 1],新字符串为 s[0 .. 2 * n-2]。对于新字符串 s 而言,s[2 * i + 1] = str[i]。

Manacher算法阐述的是这样一个思想:当我们搜索到下标 i 时,我们已经记下以下标 j(0 ≤ j ≤ i - 1) 为中点的最长回文字符串的长度为 2 * p[j] -1,即已经找到一个最大的 p[j] 使得 s[j - p[j] +1 .. j + p[j]-1] 为一个回文串。同时,我们记下下标 id 和下标 id_max 使得 id + p[id] = id_max 且 j+ p[j] ≤ id_max(0 ≤ j ≤ i - 1)。这时,我们必得到 p[i] ≥ p[2 * id - i]。

证明如下:
已知 s[id - p[id] + 1 .. id + p[id] - 1] 为回文串,
则 s[i - p[2 * id - i] + 1 .. i + p[2 * id - i] - 1] = s[2 * id - i + p[2 * id - i] - 1 .. 2 * id - i - p[2 * id - i] + 1] (注意,此时等式右边的式子当表示为 s[k .. l] 时 k > l,即在这里我们设等式右边的式子为 s[l .. k] 的翻转串),
又由于s[2 * id - i - p[2 * id - i] + 1 .. 2 * id - i + p[2 * id - i] - 1] 为回文串,
所以s[i - p[2 * id - i] + 1 .. i + p[2 * id - i] - 1] = s[2 * id - i - p[2 * id - i] + 1 .. 2 * id - i + p[2 * id - i] - 1],(注意,此时等式右边的式子当表示为 s[k .. l] 时 k < l,与上文不同)
故s[i - p[2 * id - i] + 1 .. i + p[2 * id - i] - 1] 为回文串,
即得到 p[i] ≥ p[2 * id - i]。

或许算法到这里就结束了,但是我们是否可以简化一下呢?比如,我们是否可以不用实际把特殊字符插入,直接对原字符串 str 操作来构建 p 数组呢?

这样做是可行的,事实上大多数情况下我们只是把特殊字符作为理论上的参考而无实际操作。此版本的 Manacher 伪代码如下:(仅供参考)

n := str. lengthans := id := id_max := 0p[0] := 1for i := [1, 2 * n)    p[i] := 1 + i mod 2    if i < id_max        p[i] := min(p[2 * id - i], id_max - i)    while str[i / 2 - (p[i] + 1) / 2] = str[(i - 1) / 2 + (p[i] + 1) / 2]        p[i] := p[i] + 2    if i + p[i] > id_max        id := i        id_max := i + p[i]    ans := max(ans, p[i] - 1)

由于 id_max 单升,且仅当 i + p[2 * id - i] = id_max 时有 p[i] > p[2 * id - i]。故其线性时间得证。

0 0