Manacher算法--求最长回文子串

回文和回文子串

回文串:顺着读和倒着读都一样的字符串;

回文子串:给定字符串string,若str同时满足以下两个条件:1)str是string的子串;2)str是回文串;那么str就是string的回文子串;

引出问题

要求求出上面string中最长的那个回文子串;

解决方案

方案一:枚举中心位置,对奇数位串和偶数位串分开处理;

int AllAlgorithms::longestPalindrome(const char *s, int n){
    int max,j;
    if (0 == s || n < 1) {
        return 0;
    }
    max = 0;
    
    for (int i = 0; i < n ; ++ i) {//i为回文串的中心
        for ( j = 0; ((i - j) >= 0) && ((i+j) < n); ++j) {//如果回文串的长度是奇数
            if (s[i-j] != s[i+j]) {
                break;
            }
            if ((j * 2 + 1) > max) {
                max = 2*j + 1;
                
            }
        }
        for (j = 0; ((i-j) >= 0) && ((i+j+1) < n); ++j) {//如果回文串的长度是偶数
            if (s[i-j] != s[i+j+1]) {
                break;
            }
            if ((j * 2 + 2) > max) {
                max = 2*j + 2;
                
            }

        }
    }
    return max;
    

}
该方案比较耗时累赘,因为在判断一个串是否是回文串时,需要考虑奇数和偶数位的而不同,要分开处理,就造成了代码的大量冗余,因此该方法不可取;

方案二:Manacher算法

对于一个长度为n的字符串,考虑如下问题:

长度为n的字符串,一定有n-1个“邻接间隙”,在加上首字符的前面,以及尾部字符的后面,一共有n-1+2=n+1个“gap”,然后再加上字符串本身,一共有字符n+n+1=2n+1个,这样一扩展,可以发现,它一定为奇数,因此这样做了扩展后,我们可以只考虑奇数的情况,无需考虑偶数的情况,大大简化了代码;

下面我们就来介绍有关Manacher算法的解决思路是什么样的:

介绍之前先给出两个trick:

1)为处理一致,在字符串最前面加上一位未出现过的字符,如$:


2)用一个数组P[i]来记录以字符S[i]为中心的最长回文子串向左或向右扩张的长度(包括S[i]),如:



算法的任务:在P[0...i-1]已知的前提下,计算P[i]的值,换句话说就是,在P[0...i-1]已知的前提下,能否为P[i]的计算提供有用的信息;

算法核心:

1)简单遍历,得到i个三元组{k,P[k],k+P[k]},0≤k≤i-1;以k为中心的字符形成的最大回文子串的最右位置是k+P[k]-1;

2)以k+P[k]为关键字,找出上述i各三元组中,k+P[k]最大的那个三元组,记作(id,P[id],id+P[id]),为了简化起见,进一步记mx=id+P[id],于是得到三元组为(id,P[id],mx),含义就是:在上述遍历得到的所有三元组中,向右到达最远的位置,就是mx;

3)在计算P[i]的时候,考查i是否落在了区间[0,mx)中:若i落在区间[0,mx)的右侧,说明区间[0,mx)没能控制住i,P[0...i-1]已知的前提下不能为P[i]的计算提供有用的信息;若i落在mx的左侧,则说明[0,mx)控制住了i,可以提供一些有用的信息;

下面我们用图示来看一下Manacher算法的递推关系:

1)

Manacher算法--求最长回文子串_第1张图片

记i关于id的对称点为j,j=2*id-i,如果此时满足:mx-i >P[j],则:

记my为mx关于id的对称点:my = 2*id-mx;

因为以S[id]为中心的最大回文子串为:S[my+1...id...mx-1],而S[my+1...id]与S[id...mx-1]对称,i和j也关于id对称,所以P[i]=P[j];

2)

Manacher算法--求最长回文子串_第2张图片

上图中,同样的:

记i关于id的对称点为j,j=2*id-i,如果此时满足:mx-i < P[j],则:

记my为mx关于id的对称点:my = 2*id-mx;

因为以S[id]为中心的最大回文子串为:S[my+1...id...mx-1],而S[my+1...id]与S[id...mx-1]对称,i和j也关于id对称,所以P[i]至少是等于上图中绿色框部分,即等于mx-i;

void AllAlgorithms::Manacher(char *s, int *p){
    int size = (int)strlen(s);
    p[0] = 1;
    int id = 0,mx = 1;
    for (int i = 1; i < size; i ++) {
        if (mx > i) {
            p[i] = min(p[2*id - i], mx- i);
        }
        else
            p[i] = 1;
        for (; s[i+p[i]] == s[i- p[i]]; p[i] ++);
        
        if (mx < i + p[i]) {
            mx = i + p[i];
            id = i;
        }
    }
    
}

int AllAlgorithms::min(int a, int b){
    return ((a+b) - abs(a-b))/2;
}




你可能感兴趣的:(C/C++,#,数据结构与算法,算法面试)