Manacher算法 O(n)回文子串算法

  首先把字符串的每个字符之间用和所有字符都不一样的分隔符隔开,比如‘#’,并且在最前头加一个非0的分隔符,比如*,是为了防止算p的时候越界。例如aba就变成*#a#b#a0,0是字符串的结束符。原始字符串长度有n个字符的话,就有n个#,1个*,1个0,共有n*2+2个字符。

  这样处理之后得到的串所有回文串都是奇数长度。

  用p[i]表示以下标为i的字符为中心的回文串的半边的长度,包括中心字符i,因为回文串长度都是奇数,设为2*n-1的话,p[i]就等于n。那么设i+p[i]是这个回文串能够向右扩展的位置(i+p[i]-1是这个回文串的最后一个字符)。

  从左到右扫一遍算出p[i],设maxp为当前扫过的字符的最右扩展位置,id为这个最右扩展位置的中心字符的位置。那么这个算法的关键来了,如果i<maxp的话,那么可以通过和i关于id对称的那个字符的p得到p[i]的一部分,p[i]=min(maxp-i,p[2*id-i]),为什么是两者之间取小的,比如bababae(为了方便说明没加分隔符),当前该算位置4的b,maxp=6,id=3,就是中间那个a,这时p[2]=3,但是不能说p[4]=3,第一个b不在a的回文范围内了。这时算出的p[i]不一定是最后的p[i],不在id回文内的部分也有可能关于i是回文,那么接着用一般的比较判断。也就是省去了一部分在maxp内的重复比较,复杂度O(n)。

  加了分隔符后实际的回文长度是p[i]-1,这个画一画就知道了。

void manacher(char* str){
    int len=strlen(str);
    for(int i=len;i>=0;i--){
        str[2*i+2]=str[i];
        str[2*i+1]='#';
    }
    str[0]='*';
    len=len*2+2;
    int maxp=0,id;
    for(int i=1;i<len-1;i++){
        if(i<maxp) p[i]=min(maxp-i,p[id*2-i]);
        else p[i]=1;
        while(str[i-p[i]]==str[i+p[i]]) p[i]++;
        if(i+p[i]>maxp){
            maxp=i+p[i];
            id=i;
        }
    }
}


你可能感兴趣的:(Manacher算法 O(n)回文子串算法)