Manacher算法详解

Manacher算法详解

Manacher算法是用来求最长回文长度的一个算法,不同于暴力方法的O(n*n)的时间复杂度,manacher算法可以达到O(n)是时间复杂度。

对于暴力求解一个字符串中最长的回文长度,也是有着技巧的,因为要考虑字符串为奇数偶数两种情况,所以暴力求解一般需要对字符串做一个优化,即是给每个字符之间插入一个标志符。

例如对于字符串str1 : abcdcbeee

假如我们用#来作为标识符,经过优化后的字符串str2为#a#b#c#d#c#b#e#e#e#,我们求str2每个字符的回文长度,而后将所求的最大一个回文长度除以2,得到的结果即是该字符串最长的回文长度。

如何将str1 变成str2 代码如下:

    public static char[] manacherString(String str){
        char[] chars = str.toCharArray();
        char[] res = new char[chars.length*2+1];
        int index = 0;
        for (int i=0;i<res.length;i++){
            res[i] = (i & 1) == 0 ? '#' : chars[index++];
            System.out.print(res[i]+" ");
        }
        System.out.println();
        return res;
    }

上述代码将所有下标为偶数的位置置为#,而奇数位依次填充原数组的值。

manacher算法是如何做到时间复杂度为O(n)的呢,它充分利用了回文对称的性质。

我们首先定义两个变量c和r,c代表回文中心,r代表c的回文右边界。这里需要注意的是c并不是当前回文的位置,而是r对应的回文中心。而r是最右的回文边界。

这里要做到的一件事就是r永不回退,意思就是r只能往右边走,不能往左边退,当当前位置i的右回文半径超过r时,我们更新c为i,更新r为当前回文中心的右边界。

最开始,我们将c和r初始化为-1,而后我们遍历整个字符串数组,对于没有在r范围内的i,我们暴力扩,求其r,而后更新r和c。

对于i在r中的情况,又具体分为三种情形,第一种i关于c对称的i‘的回文半径在r中,那么i的回文半径就等于i’的回文半径。

第二种i关于c对称的i‘的回文半径在r外,则i的回文半径为r-i。这两种情况的时间复杂度都为O(1)。

第三种i关于c对称的i‘的回文半径在r上,这时就需要继续暴力比较了。

关于算法的证明,简单在草稿纸上画一画,就很容易看出这四种情况,这里不做赘述。

代码如下:

    public static int maxLcpsLenth(String str){
        char[] chars = manacherString(str);
        int[] m = new int[chars.length];
        int r = -1;
        int c = -1;
        int max = 0;
        for(int i =0 ;i<chars.length;i++){
            m[i] = r > i ? Math.min(m[2*c-i],r-i):1;
            while (i-m[i] >-1 && i+m[i] < chars.length){
                if(chars[i+m[i]] == chars[i-m[i]]){
                    m[i]++;
                }else {
                    break;
                }
            }
            if(i+m[i]>r){
                r = i+m[i];
                c = i;
            }
            max = Math.max(max,m[i]);
        }
        return max -1 ;
    }

这里定义的一个int型的数组,用来存储每一个坐标的回文半径长度,由于我们的chars数组是原数组经过扩展边长得到的,所以最后求得的回文长度直接用回文半径减一便可以得到。

猜你喜欢

转载自blog.csdn.net/qq_38180223/article/details/81233357