Manacher算法理解。

 

1.用途:

寻找字符串中最长回文子串(连续)

注:如果要求寻找字符串中最长回文子序列(不连续)的话,好像可以使用reverse翻转之后用最长子序列的算法(DP)来求。

2.理解:

2.1构造忽略奇偶数的序列填充。

这是一个挺好的想法,就是,你如果采用字符为中心,向左右查找的话,对于abcba和abba这两种回文字符串的识别规则是不同的。

前者你需要取最中间的“c”向左右查找。后者你需要同时选取"b""b"向左右查找(没有最中间的"单个字符")。

不知道这么说能不能理解。不过总之就是要分开讨论。

在manacher算法中,我们通过填充特殊符号(一般是"#")来规避这个分开讨论。

abba   ->   #a#b   #   b#a#

abcba   ->    #a#b#   c   #b#a#

可见填充之后就都可以找到最中间的“一个”字符来处理了。

2.2构造记录数组LEN

本算法通过一个数组(与填充后的字符串等长,字符一一对应)来记录,当前下标 i 处的最长回文子串的右端点到 i 的长度。

注:len[i]-1,即为未填充的原数组的该回文子串的长度。

证明:len[i] *2 -1是填充后的数组的该回文子串的长度,减去填充的特殊符号#(共len[i]个)即为所求。

所以,只要我们得到了len数组的值,我们就可以求出最长回文子串长度了。

那么,这个len的值怎么求呢?

2.2.1从左向右依次计算:

假设我们要计算  i  处的len[i]值,那么我们已经求得了任意的 j<i 处的len[j]的值。

其中,我们记录一个右端点最大的len[j]的子串。右端点最大意味着这个以J为中心的子串覆盖的最远。

以下分情况讨论:

2.2.1.1

如果我们当前所计算的  i  位置,在覆盖的范围之内(i<j+len[j]),那么这就意味着,i关于j的对称点p,也在j覆盖的范围之内(左边)。

此时,我们需要查找我们之前计算过的len[p]的值,确定他是否越过len[j]的左边界。

如果p的回文子串越过,则i的回文子串也可能会越过右边界。(此时我们需要从右端点+1的位置开始一一适配。并判断是否要更新最大右端点,最大右端点对应的 i 以及len[i])

否则(不越过,证明p的回文子串的大小确定,对应的i的len[i]等于这个len[p])

如图(左侧对称点子串未越过边界):

如图(左侧对称点子串越过边界)

总而言之,这种思想其实挺类似KMP算法中的重用思想的。就是如果len[(i的对称点j)]确定的范围不会越过左侧P0的界,就可以重用。否则不能重用。要重新算一下。我的理解是这样。

2.2.1.2

如果我们当前所计算的 i 位置在覆盖的范围之外。

那么就不能借用以前的最大右端点和最长子串的信息来减少适配了。

所以我们就需要一一适配。

并更新相应信息。

3.小技巧。

为了防止越界,据说,我们通常会在填充后的字符串的最前加上一个区别于填充符号的特殊符号(一般可以用$)

即:$#a#b#a#@

结尾的话...考虑到/0,其实不知道不加可不可以。但一家人就是要整整齐齐的啊。所以我也加上吧!(此处应与首部不同)

4.如果有哪里理解的不对的话还请指正。

代码待补充。

    string longestPalindrome(string s){
        if(s.length()<=1){return s;}
        string res="$#";
        for(int i=0;i<s.length();i++){
            res+=s[i];
            res+='#';
        }
        res+='%';
        //得到一个填充之后的字符串。
     
        vector<int> len(res.length(),0);
        //记录最长的长度和起点。
        int maxlen=-1;
        int maxbeg=-1;
        //记录最右端点和对应中点
        int maxright=-1;
        int maxmiddle=-1;
        for(int i=0;i<len.size();i++){
            if(i>=maxright){//超出右界限
                int temp=judge(res,i);
                len[i]=temp;
                if(temp+i>maxright){
                    maxmiddle=i;
                    maxright=temp+i;
                }
            }
            
            else if(i<maxright && len[2*maxmiddle-i]<maxright-i ){//左对称点不超过界限。
                len[i]=len[2*maxmiddle-i];
            }
            else {//左对称点超过界限
                int temp=judge(res,i);
                len[i]=temp;
                if(temp+i>maxright){
                    maxmiddle=i;
                    maxright=temp+i;
                }
            }
            if(len[i]>maxlen){maxlen=len[i];maxbeg=i;}
        }
    
        //我可以遍历然后删掉#啊!。
        //输出最长回文子串    
        string res2="";
        //cout<<maxbeg<<" "<<maxlen<<endl;
        for(int i=maxbeg-maxlen;i<=maxbeg+maxlen;i++){
            if(res[i]!='#')res2+=res[i];
        }
        return res2;
    }
    int judge(string s,int i){
        int beg=i-1;
        int end=i+1;
        while(beg>=0 && end<s.length() && s[beg]==s[end]){
            beg--;end++;
        }
        //cout<<"print "<<end-i-1<<endl;
        return end-i-1;
    }

参考以及图示来源:

https://blog.csdn.net/dyx404514/article/details/42061017

猜你喜欢

转载自blog.csdn.net/dzx1997/article/details/81113185