Manacher's Algorithm 的理解

链接:https://www.jianshu.com/p/7dacc9a3c9a0
在 leetcode 刷题刷到求字符串的最长回文字串,而马拉车算法(Manacher’s Algorithm), 正是这道题的最佳答案。
如果我们不用马拉车算法,比较快的方法来求出最长回文字串的算法是:遍历字符串(遍历是创建变量 i),以 i 为中心,对比 i-n 和 i+n 是否相等。这种算法的时间复杂度为O(n*n)。而马拉车算法可以理解为是对这种方法的进一步改良。
要理解马拉车算法,首先要理解回文字串的特点: 正序读和反序读都一样,也可以理解为数学中的轴对称
先来瞄一瞄代码

vector<int> manacher(string s) {
        //(1)第一步
        //insert #
        string Ma = "#";
        for(int i = 0; i < s.length(); i++) {
            Ma += s[i];
            Ma += "#";
        }
        
        int count = (int)Ma.length();
        vector<int> Mp(count, 1);
        int ID = 0, Mx = 1;
        for(int i = 1; i < count - 1; i++) {
            //fixme: array overflow
            //(2)第二步
            Mp[i] = i < Mx ? min(Mp[2*ID-i], Mx-i) : 1;
            //(3)第三步
            //i+Mp[i] < count && i - Mp[i] >= 0 防止越界
            while (i+Mp[i] < count && i - Mp[i] >= 0 &&Ma[i + Mp[i]] == Ma[i - Mp[i]]) {
                Mp[i]++;
            }
            if(Mx < (i + Mp[i])) {
                Mx = i + Mp[i];
                ID = i;
            }
        }
        return Mp;
    }

1.我们先理解 (1)第一步 和 Manacher’s Algorithm 的返回值:
(1)第一步
在字符串每隔一个字符插入一个字符串中没有的字符,这里定为 #。举个栗子:
abbcdde 就变为 #a#b#b#c#d#d#e#
什么要这样做呢?
回文串的长度可以是奇数或偶数(下面就称为 奇数回文串 和 偶数回文串)。比如在 babccba 中, bab 是奇数回文字串, abccba 是偶数回文字串。在插入 # 之后(下面把插入 # 之后的字符串称为 Ma),回文子串就只有奇数回文串(如果 Ma 的回文字串中间字符是 “#”,对应的是原字符串的偶数回文字子串,否则就是对应的奇数回文字串。)这样就不用分两种奇偶两种情况讨论回文子串。
(2)Manacher’s Algorithm 的返回值
算法的返回值是一个数组(下文叫 Mp),Mp 的长度与 Ma 一致,Mp[i] 记录的是以 Ma[i] 为中心的回文字串的长度一半(下文称之为 radius,radius = 长度/2+1);
那我们得到 Mp 数组怎么对应回对应原字符串的位置呢?
因为说起来比较啰嗦,自己就偷了个懒,所以此处只贴出公式,有疑问看官的请留言,我再补上。
location = (i - Mp[i] + 1)/2;
length = radius - 1;
2.理解(2)第二步
这一步是算法的核心。
看下图,在 for 循环中,遍历到了 i,i 所在的位置看下图。
Mx: 是遍历到目前,子回文串的右端达到最远的位置。
ID: 是 Mx 对应的子回文串的中点。

如果能理解上面两个变量的概念的话,来看这句核心代码。
Mp[i] = i < Mx ? min(Mp[2ID-i], Mx-i) : 1;
如果 i < Mx, Mp[i] 等于 Mp[2
ID-i] 和 Mx-i 中的最小值,否则就等于 1。

先理解 i < Mx 的含义:i < Mx,证明 i 在 ID 对应子回文串的范围之内,我们知道回文串的特点为轴对称,
所以 ID 的前面,必有一个和 i 对称的 i_left, 而且我们已经知道了 Mp[i_left]。
那是不是就可以得出 Mp[i] = Mp[i_left] 呢?
不是的。因为 Mp[i_left] 对应的子串范围有可能超出 Mp[ID] 对应的子串范围,超出的部分就无法确定了。所以加了限制 Mx-i。而子串最短是 1,所以有了这句代码

3.理解(3)第三步
在第二步得出 Mp[i] 的下限值,这一步就是在这个基础上再比较,看看是否能扩长回文字串。
4.性能分析
时间复杂度:O(n)

看起来像是两个循环的嵌套,为什么是O(n)呢?
因为算法只有遇到还没有匹配的位置时才进行匹配,已经匹配过的位置不再进行匹配,所以>对于Ma字符串中的每一个位置,只 进行一次匹配,所以 Manacher 算法的总体时间复杂>度为O(n),其中n为 Ma 字符串的长度,由于 Ma 的长度事实上是 s 的两倍,所以时间>复杂度依然是线性的。摘自百度百科

空间复杂度:O(n)

猜你喜欢

转载自blog.csdn.net/WitsMakeMen/article/details/88915950