Manacher算法解析

387 阅读3分钟

1.回文问题

回文问题是一个常见的算法问题,我们在刷题的时候经常看见看见此类问题,一般常见的解法是暴力解法,

暴力解法1.0

  • 暴力解法的方法就是将字符串拆分开来然后逐个进行判断是否为回文字符串,但是这种解法还是有一个很大的bug,当是奇数的时候,就会造成并不能计算到最长的回文字符串。

​ 所以需要对这种暴力的解法做一个优化,对其进行优化 2.0

  • 将字符串进行改变,在每个字符串的左右都加上一个特殊字符(随便什么字符都行),然后再进行逐个查找回文字符串,这样就能保证查到的回文字符串一定是最大的。但是这样优化后的暴力解法的时间复杂度还是堪忧,为O(N^2)。

2.manacher算法

此算法是为了优化暴力法为生的一个算法,算法包含了3个点

  • 1.回文半径数组parr[]:存放每个回文字符串的长度

  • 2.回文最右边界R,标记已经遍历出的最大回文字符串的右边界

  • 3.回文中心点C ,标记已经遍历出的最大回文字符串的右边界

在遍历的时候将字符串的转为字符数组,下标 i 的位置对于右边界R有3个情况

  • 1.下标 i 在右边界R外,为计算此下标为中心的回文字符串只能使用暴力法

  • 2.下标 i 的关于C的对称点 i‘ 回文长度正好在左边界上,找到 i 相对于 中心点 C 的对称点 i‘ ,获得parr[i']的长度,如果其的回文范围在右边界为R的最大回文字符串的范围内时,下标 i 为中心点的最大回文字符串长度为parr[i']。

  • 3.下标 i 的关于C的对称点 i‘ 回文长度正好在左边界上,则需要对边界R外面的数继续进行暴力对比从而得到下标 i 为中心点的回文字符串的长度。

代码如下:

     * 解法一: 暴力法 将给定的字符串给全部进行加工,在每个字符的中间都加上一个特定的字符,然后对每个字符进行发散查找,记下每个回文字符串的长度,最后比较所有的回文字符的长度 O(N^2)
     *
     *
     * 解法二:manacher算法,设置三个条件,1.一个回文半径数组parr[] 2.回文最右边界 R 3.回文的中心点
     *                     parr[]数组是存放所有的回文字符串的长度,如果只有一个的话则就为1,
     *                     R代表的是最长回文串的最右边界,回文半径等于R-中心点
     *                     C代表每个回文串的中心点
     *                     R 和 C 的初始值都为-1
     *O(N)
     */



    public static int manacherDetail(String s){
        if(s == null || s.length() == 0){
            return 0;
        }
        char[] str= manacherString(s);
        int[] parr = new int[str.length];
        int R = -1;
        int C = -1;
        int max = Integer.MIN_VALUE;
        for(int i = 0;i<str.length;i++){
            //每个parr[]中的元素初始化都为1,如果当i点处于目前最大回文字符串的右边界R内部时,则取以最大回文字符串的中心点C找他在parr数组的对称点
            //如果改对称点的回文区域范围已经有在最大回文字符串的区域范围外面时,就取二者的最小值
            parr[i] = R>i ?Math.min(parr[2*C-i],R-i):1;
            //在每个parr元素已有的范围内去判断以外的范围是否还能不能构成回文串,如果可以,则回文半径要加1,不行则跳出循环
            while (i + parr[i] < str.length && i - parr[i] > -1) {
                if (str[i + parr[i]] == str[i - parr[i]]) {
                    parr[i]++;
                }else {
                    break;
                }
            }
            //如果出现更大的回文字符串的时候,移动最右边界,并且将中心点改变
            if (i + parr[i] > R) {
                R = i + parr[i];
                C = i;
            }
            //给max赋值为最大的回文半径
            max = Math.max(max, parr[i]);
        }
        //最后返回的数由于有特殊字符的加入,需要减去1
        return max - 1;
    }

    public static char[] manacherString(String str){
        char[] charArr = str.toCharArray();
        char[] res = new char[str.length() * 2 + 1];
        int index = 0;
        for (int i = 0; i != res.length; i++) {
            res[i] = (i & 1) == 0 ? '#' : charArr[index++];
        }
        return res;
    }