数据结构--Manacher算法(最长回文子串)

在字符串中求出其最长回文子串

可能是奇回文也可能是偶回文,要考虑全面

暴力解法:(因为存在奇回文和偶回文的问题,所以不好找,有一个技巧,就是向字符串中每个字符之间添加一个符号(任意符号,也可以是字符串中的符号),然后在每个位置向两端开始扩充)

答案就是最大值/2


判断的前期处理,在字符串的左右都加一个 #
* 11311 --> #1#1#3#1#1#
* 然后以每个字符为基础,向两边开始扩充,得到此字符的回文子串,最后返回最大的回文子串的长度 / 2

代码

public static int manacher1(String str){
        if(str == null || str.length() == 0) return -1;
        char[] ch = new char[str.length() * 2 + 1];
        /*for(int i = 0, j = 0; i < ch.length && j < str.length(); i++){
            ch[i++] = '#';
            ch[i] = str.charAt( j++ );
        }*/
        int index = 0;
        for(int i = 0; i < ch.length; i++){
            ch[i] = (i & 1) == 0 ? '#' : str.charAt( index++ );
        }
        int max = Integer.MIN_VALUE;
        for(int i = 0; i < ch.length; i++){
            int count = 1;
            for(int x = i - 1, y = i + 1; x >= 0 && y < ch.length; x--, y++){
                if(ch[x] == ch[y]){
                    count += 2;
                }else{
                    break;
                }
            }
            max = max > count ? max : count;
        }
        return max;
    }

  

  

Manacher算法: 字符串中每个字符串之间也要加上一个字符

回文直径:从某个位置开始向两边扩的最大长度

1. 回文半径数组:arr[],以每个位置为中心能扩出来的回文半径的长度

2. 最右回文右边界R:所有回文半径中,最靠右的位置

  开始位置为-1

3. 回文右边界的中心  当前回文右边界到达最右边时,是以哪个位置为中心进行扩充的

算法:

1.当前位置为 i 

可能性1:i 在回文右边界R外,则采用暴力扩充

可能性2

i 在回文右边界R内,且 i 关于回文右边界中心的对称位置 i’ 的回文中心的半径  全部在L 和 R内部,

此时 i 位置的回文区域的半径和 i' 一样

②、:i 在回文右边界R内,且 i 关于回文右边界中心的对称位置 i’ 的回文中心的半径  部分在L 和 R内部

此时 i 位置的回文半径就是 i 到 R

③、:i 在回文右边界R内,且 i 关于回文右边界中心的对称位置 i’ 的回文中心的半径就是L

此时 i 位置的回文半径至少是 i 到 R,后面的情况是不确定的。

 ①

 

 

/**
     *  manacher算法
     *  情况1:当前点 i 在最右回文右边界外面
     *      采用暴力扩
     *  情况2:当前点 i 在最右回文右边界里面
     *
     */
    public static int manacher2(String str){
        if(str == null || str.length() == 0) return -1;
        char[] ch = new char[str.length() * 2 + 1];
        int index = 0;
        //对字符串进行处理  11311 -->  #1#1#3#1#1#
        for(int i = 0; i < ch.length; i++){
            ch[i] = (i & 1) == 0 ? '#' : str.charAt( index++ );
        }
        for(int i = 0; i < ch.length; i++){
            System.out.print(ch[i]);
        }
        System.out.println();
        //最右回文右边界
        int maxRight = -1;
        //当前回文右边界第一次出现的中心
        int center = 0;
        //用来记录各点的回文长度  回文半径数组
        int[] flags = new int[ch.length];
        int max = 0;
        for(int i = 0; i < ch.length; i++){
            // i 在最右回文右边界外面  暴力扩
            if(i > maxRight){
                int x = i - 1, y = i + 1;
                while( x >= 0 && y < ch.length){
                    if(ch[x] != ch[y]) break;
                    x--;
                    y++;
                }
                maxRight = maxRight > y - 1 ? maxRight : y - 1;
                center = maxRight > y - 1 ? center : i;
                flags[i] = y - 1 - i;
            } else{
                // i 在最右回文右边界里面
                //1. 其关于回文中心的对称位置 mirrorI 的回文右边界在当前最右回文右边界的里面
                int mirrorI = 2 * center - i;
                if(flags[mirrorI] < maxRight - i){
                    flags[i] = flags[mirrorI];
                    //2. 其关于回文中心的对称位置 mirrorI 的回文右边界在当前最右回文右边界的外面
                } else if(flags[mirrorI] > maxRight - i){
                    flags[i] = maxRight - i;
                } else{
                    //3. 其关于回文中心的对称位置 mirrorI 的回文右边界等于当前最右回文右边界 从maxRight处继续向外边扩
                    int x = i - flags[mirrorI] - 1, y = maxRight + 1;
                    while (x >= 0 && y < ch.length){
                        if(ch[x] != ch[y]) break;
                        x--;
                        y++;
                    }
                    maxRight = maxRight > y - 1 ? maxRight : y - 1;
                    center = maxRight > y - 1 ? center : i;
                    flags[i] = y - 1 - i;
                }
            }
            max = Math.max( max, flags[i] );
        }
        return max;
    }

  

优化版

public static int manacher3(String str){
        if(str == null || str.length() == 0) return 0;
        char[] ch = new char[str.length() * 2 + 1];
        int index = 0;
        for(int i = 0; i < ch.length; i++){
            ch[i] = (i & 1) == 0 ? '#' : str.charAt( index++ );
        }

        //回文半径数组
        int[] flags = new int[ch.length];
        //最右回文右边界
        int maxRight = -1;
        //第一次取到最右回文右边界时的位置
        int center = 0;

        //最长回文子串长度
        int max = Integer.MIN_VALUE;
        for(int i = 0; i < ch.length; i++){
            //判断当前 i 位置与最右回文右边界maxRight的大小,若 i 在 maxRight外部,则 当前回文半径 = 1,
            // 否则当前回文半径 = i 对称点的半径 和 最右回文右边界中,较小的那个,然后,均向外扩,
            // 那么 i的对称点的回文半径在maxRight外部和内部均解决,只剩下 回文半径 = maxRight情况,
            // 然后以maxRight为起始点,开始向外扩
            flags[i] = maxRight > i ? Math.min(flags[2 * center - i], maxRight - i) : 1;
            while(i + flags[i] < ch.length && i - flags[i] >= 0){
               if(ch[i + flags[i]] == ch[i - flags[i]]) {
                   flags[i]++;
               }
               else break;
            }
            if(i + flags[i] > maxRight){
                maxRight = i + flags[i];
                center = i;
            }
            max = Math.max( flags[i], max );
        }
        return max - 1;
    }

  

posted @ 2018-05-02 23:08  SkyeAngel  阅读(145)  评论(0编辑  收藏  举报