编程语言
首页 > 编程语言> > Re:Manacher:: 老司机教会了女同学「马拉车算法」的正确姿势

Re:Manacher:: 老司机教会了女同学「马拉车算法」的正确姿势

作者:互联网

Manacher‘s Algorithm

关于

马拉车算法(以下皆简称mnc)是用来 查找一个字符串的最长回文子串的线性方法 ,由一个叫 Manacher 的人在 1975 年发明的,这个方法的牛逼之处在于将时间复杂度提升到了 线性

你可以理解为O(n).

 

中心思想及流程

使用“中心扩散法”,

“中心扩散法”的思想:回文串可分为奇数回文串和偶数回文串,它们的区别是:奇数回文串关于它的“中点”满足“中心对称”,偶数回文串关于它“中间的两个点”满足“中心对称”。

但为了避免奇偶套路,可以直接在字符串每个字符间隙和头尾间加上”分隔符

 

 

step 1:对原字符串预处理

就如前文一样,加入分隔符,如对于一个字符串'abbaab',加入分隔符后为‘#a#b#b#a#a#b#’.

 

对这一点有如下说明:

1、分隔符是一个字符,种类也只有一个,并且这个字符一定不能是原始字符串中出现过的字符,然后满足这个条件后,这个分隔符如何处置就在你了╮(─▽─)╭;

2、加入了分隔符以后,使得“间隙”有了具体的位置,方便后续的讨论,并且新字符串中的任意一个回文子串在原始字符串中的一定能找到唯一的一个回文子串与之对应,当然也要看位置,因此对新字符串的回文子串的研究就能得到原始字符串的回文子串;

3、新字符串的回文子串的长度一定是奇数)自己算;

4、新字符串的回文子串一定以分隔符作为两边的边界,因此分隔符起到“哨兵”的作用。

 

设原字符串为s,新字符串为s1,则

s.size()*2+1==s1.size();

floor(s1.size()/2)==s.size();

 

 

step 2:计算辅助数组p

辅助数组 p 记录了新字符串中以每个字符为中心的回文子串的信息。及回文半径

 

手动的计算方法仍然是“中心扩散法”,此时记录以当前字符为中心,向左右两边同时扩散,记录能够扩散的最大步数。

 

以'abab'为例,列出如下表格

index 0 1 2 3 4 5 6 7 8
char  # a # b # a # b #
p  1 2 1 4 1 2 1 2

1

 

发现 p 最大是4,则原字符串最长回文串长度为3,回文串输出可进一步实现,在此不过多赘述。

 

为什么p_max就是最长回文串长度呢?

将回文中心(包括回文中心)一侧的‘#’与紧挨的字符两两配对,回文中心可以是分隔符。

不难看出  对数*2-1=回文串长度。不论奇偶都一样。

 

那么下面我们就来看如何求p数组,需要新增两个辅助变量 mx 和 id,其中 id 为能延伸到最右端的位置的那个回文子串的中心点位置,mx 是回文串能延伸到的最右端的位置,需要注意的是,这个 mx 位置的字符不属于回文串,所以才能用 mx-i 来更新 p[i] 的长度而不用加1,由 mx 的更新方式 mx = i + p[i] 也能看出来 mx 是不在回文串范围内的,这个算法的最核心的一行如下:

 

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

 

可以这么说,这行要是理解了,那么马拉车算法基本上就没啥问题了,那么这一行代码拆开来看就是

如果 mx > i, 则 p[i] = min( p[2 * id - i] , mx - i )

否则,p[i] = 1

当 mx - i > P[j] 的时候,以 S[j] 为中心的回文子串包含在以 S[id] 为中心的回文子串中,由于 i 和 j 对称,以 S[i] 为中心的回文子串必然包含在以 S[id] 为中心的回文子串中,所以必有 P[i] = P[j],其中 j = 2*id - i,因为 j 到 id 之间到距离等于 id 到 i 之间到距离,为 i - id,所以 j = id - (i - id) = 2*id - i

 

 

当 P[j] >= mx - i 的时候,以 S[j] 为中心的回文子串不一定完全包含于以 S[id] 为中心的回文子串中,但是基于对称性可知,下图中两个绿框所包围的部分是相同的,也就是说以 S[i] 为中心的回文子串,其向右至少会扩张到 mx 的位置,也就是说 P[i] = mx - i。至于 mx 之后的部分是否对称,就只能老老实实去匹配了,这就是后面紧跟到 while 循环的作用。

 

 

对于 mx <= i 的情况,无法对 P[i] 做更多的假设,只能 P[i] = 1,然后再去匹配了。

 

代码如下

#include <vector>
#include <iostream>
#include <string>

using namespace std;

string Manacher(string s) {
    // Insert '#'
    string t = "$#";
    for (int i = 0; i < s.size(); ++i) {
        t += s[i];
        t += "#";
    }
    // Process t
    vector<int> p(t.size(), 0);
    int mx = 0, id = 0, resLen = 0, resCenter = 0;
    for (int i = 1; i < t.size(); ++i) {
        p[i] = mx > i ? min(p[2 * id - i], mx - i) : 1;
        while (t[i + p[i]] == t[i - p[i]]) ++p[i];
        if (mx < i + p[i]) {
            mx = i + p[i];
            id = i;
        }
        if (resLen < p[i]) {
            resLen = p[i];
            resCenter = i;
        }
    }
    return s.substr((resCenter - resLen) / 2, resLen - 1);
}

int main() {
    string s1 = "12212";
    cout << Manacher(s1) << endl;
    string s2 = "122122";
    cout << Manacher(s2) << endl;
    string s = "waabwswfd";
    cout << Manacher(s) << endl;
}
点击展开

 

标签:子串,Manacher,mx,Re,分隔符,字符串,女同学,id,回文
来源: https://www.cnblogs.com/codingxu/p/14087754.html