Manacher's Algorithm 马拉车算法

2019-04-02

马拉车算法是用来查找一个字符串的最长回文子串的线性方法,是一个叫 Manacher 的人在 1975 年发明的,这个方法的最大贡献是在于将时间复杂度提升到了线性,这是非常了不起的

回文介绍

回文就是正读倒读都一样的字符串,如 “level”, “noon”

求最长回文串,需要遍历去求以每个字符为中心,向两边寻找回文子串
获得最大回文子串的时间复杂度 O(n^2)

而马拉车算法的时间复杂度为 O(n)

马拉车算法

1、预处理

解决奇偶数回文问题)在每一个字符的左右都加上一个特殊字符,如 #

level   ->  #l#e#v#e#l#
noon    ->  #n#o#o#n#

处理之后得到的字符串的个数都是奇数个,之前是回文的之后还是回文,不用分情况讨论了

2、求回文子串的半径

处理之后得到的字符串 t
                    #  a  #  a  #  b  #  b  #  a  #  b  #  b  #  a  #
位置 i
                    0  1  2  3  4  5  6  7  8  9  10 11 12 13 14 15 16
以 t[i] 字符为中心的回文子串半径数组 p
                    1  2  3  2  1  2  5  2  1  8  1  2  5  2  1  2  1

怎么计算呢?
如位置 9 的字符是 a,本身算 1,因为 a 字符串本身就是对称的
然后比较 a 左右的字符,相同则 p[9]++,不断向外扩展,最后得到 p[9] = 8

求回文子串的半径有什么用?
以 t[9] = a 为中心的回文是 #a#b#b#a#b#b#a#,原字符串回文 abbabba 长度 7
刚好是 p[9] - 1,即回文子串的半径减 1(#a#b#b#a#b#b#a#
这是个通用规律,可以思考、去求证下

算法核心介绍

引入两个辅助变量 mx 和 id
mx 是之前计算中最长回文子串的右端点的最大值
id 为取得 mx 的回文中心位置

mx > i

p[2 * id - i]   对称点 j 的回文子串的半径
mx - i          位置 i 到 mx 的距离

如果 p[2 * id - i] < j - mx' = mx - i
    根据对称性,即以 j 为中心的回文包含在以 id 为中心的回文里面
    id 到 mx、mx' 左右对称

    ==> p[i] = p[j] = p[2 * id - i]

mx > i

如果 p[2 * id - i] >= j - mx' = mx - i
    表示 i 为中心的回文右边界超过了 mx
    因为 mx 右侧的字符是未计算的,需要再循环比较

    ==> p[i] = mx - i

i > mx

无法对 p[i] 做更多的假设,只能 p[i] = 1,然后再去匹配了

算法核心代码

p[i] = mx > i ? min(p[2 * id - i], mx - i) : 1;

具体实现

package main

import (
    "bytes"
    "fmt"
    "strings"
)

func main() {
    s := "aabbabba"
    fmt.Println(manacher(s))
    // abbabba
}

// 马拉车算法
func manacher(s string) string {
    char := byte('#')
    s = changeStr(s, char)

    // 存储以字符 N[i] 为中心的最长回文字串的最右字符到 N[i] 的长度
    p := make([]int, len(s))
    // mx 之前计算中最长回文子串的右端点的最大值,id 为取得 mx 的回文中心位置
    var id, mx int
    // 最大回文中心位置
    maxCenterIndex := 1

    // 去除边界 ^ $
    for i := 1; i < len(p) - 1; i++ {
        // j 是 i 以 id 为中心的对称点
        j := 2 * id - i

        // i 处在右边界 mx 的左边
        if mx > i {
            if mx - i > p[j] {  // 对称点 j 的回文串没有越界,根据对称关系赋值
                p[i] = p[j]
            } else {            // 越界,则先赋值 mx - i
                p[i] = mx - i
            }
        }

        // 循环比较
        for s[i + p[i]] == s[i - p[i]] {
            p[i]++
        }

        // i 的右边界超出 mx
        if i + p[i] > mx {
            mx = i + p[i]
            id = i
        }

        // 计算最大回文中心位置
        if p[i] > p[maxCenterIndex] {
            maxCenterIndex = i
        }
    }

    // 最大回文串长度
    //maxLength := p[maxCenterIndex] - 1

    // 返回最长回文
    start := maxCenterIndex - p[maxCenterIndex] + 1
    end := maxCenterIndex + p[maxCenterIndex]
    substr := s[start:end]

    return strings.Replace(substr, string(char), "", -1)
}

// 改变字符串
func changeStr(s string, char byte) string {
    var new bytes.Buffer

    new.WriteByte('^')
    for _, b := range []byte(s) {
        new.WriteByte(char)
        new.WriteByte(b)
    }
    new.WriteByte(char)
    new.WriteByte('$')

    return new.String()
}