Mancher算法-最长回文子串-LeetCode5

利用辅助数组P(数组长度与字符串长度相同)记录以每个字符为中心的最长回文半径

已知字符下标(i)和以该字符为中心的最长回文半径,则可获取最长回文子串。

但该算法存在一个问题:只能求长度为奇数的回文子串。

解决方法:每个相邻字符用特殊字符分割(如:#),包括首尾

例:

原字符串:  a b a a b

新字符串:# a # b # a # a # b #

   P[]: 1 2 1 4 1 2 5 2 1 2 1 

核心问题:需计算辅助数组P[] (由于用特殊字符分隔相邻字符,则数组P长度为2*str.length() + 1).

计算辅助数组P:

源码:

public class Main {

    public static void main(String[] args) {
    System.out.println(longestPalindrome("babad"));
    }

    /**
     * Mancher 算法:
     * 辅助数组P,记录以每个字符为中心的最长回文半径
     * 则只能求回文子串长度为奇数的回文子串,如aba
     * 解决方法:每个相邻字符用特殊字符分隔(如:#),包括字符串首尾
     *  原字符串: a b a a b
     *  新字符串:#a#b#a#a#b#
     *  P[]:12141252121
     * 核心问题:求辅助数组P
     * */
    public static String longestPalindrome(String s) {
    int len = s.length();
    int newLen = 2*len +1;

    //处理字符串
    char[] newStr = new char[newLen];
    for(int index = 0; index < len; index++){
    newStr[2*index] = '#';
    newStr[2*index + 1] = s.charAt(index);
    }
    newStr[2*len] = '#';

    //求辅助数组P
    int[] p = new int[newLen];
    for(int index = 0; (index < 2*len + 1); index++){
    int right = index + 1;
    while((2*index-right) >= 0 && right < newLen && newStr[2*index-right] == newStr[right])
    right++;
    p[index] = right - index;
    }

    //获取最长回文子串半径
    int i = 0;//最长回文子串中心字符下标
    for(int index = 0; index < newLen; index++){
    i = p[index] > p[i] ? index : i;
    }

    //获取最长回文子串
    String palindrome = "";
    for(int index = i+1-p[i]; index < i + p[i]; index++){
    palindrome += '#' == newStr[index] ? "" : newStr[index];
    }

    return palindrome;
    }
}


Mancher算法,求辅助数组P[]时,避免了重复计算,即根据P[0]-P[index-1],推断出以index位置的字符为中心的最长回文子串从何处开始判断,而并非每次都从index位置相邻的两个字符开始判断

Mancher算法计算辅助数组P[]:

需要两个变量

maxi:记录0-(index-1)个字符中,最长回文子串最右边界的字符下标

r:即P[maxi],0-(index-1)个字符中,最长回文子串最右边界的字符的最长回文子串半径

max:maxi + r - 1,0-(index-1)个字符中,最长回文子串的最右边界 

代码如下: public class Main {

    public static void main(String[] args) {
        System.out.println(longestPalindrome("baabd"));
    }

    /**
     * Mancher 算法:
     * 辅助数组P,记录以每个字符为中心的最长回文半径
     * 则只能求回文子串长度为奇数的回文子串,如aba
     * 解决方法:每个相邻字符用特殊字符分隔(如:#),包括字符串首尾
     *  原字符串: a b a a b
     *  新字符串:#a#b#a#a#b#
     *  P[]:12141252121
     * 核心问题:求辅助数组P[]
     * */
    public static String longestPalindrome(String s) {
        int len = s.length();
        int newLen = 2*len +1;

        //处理字符串
        char[] newStr = new char[newLen];
        for(int index = 0; index < len; index++){
            newStr[2*index] = '#';
            newStr[2*index + 1] = s.charAt(index);
        }
        newStr[2*len] = '#';

        //求辅助数组P
        int[] p = new int[newLen];
        int maxi = 0;       //0-(index-1)个字符中,最长回文子串最右边界的字符下标
        int r = p[0] = 1;   //0-(index-1)个字符中,最长回文子串最右边界的字符的最长回文子串半径
        int max = maxi + r - 1; //0-(index-1)个字符中,最长回文子串的最右边界
        for(int index = 1; (index < newLen); index++){
            //判断以当前字符为中心,求回文子串的右边起始位置,避免重复计算
            int temp = 2*maxi-index < 0 ? 0 :
                    (max > index ? (p[2*maxi-index] < (max-index) ? p[2*maxi-index] : (max-index))-1 : 0);
            int right = index + 1 + temp;
            //向两边扩展,求回文子串
            while((2*index-right) >= 0 && right < newLen && newStr[2*index-right] == newStr[right])
                right++;
            p[index] = right - index;

            if(max < index + p[index] - 1){
                maxi = index;
                r = p[index];
                max = maxi + r - 1;
            }
        }

        //获取最长回文子串半径
        int i = 0;//最长回文子串中心字符下标
        for(int index = 0; index < newLen; index++){
            i = p[index] > p[i] ? index : i;
        }

        //获取最长回文子串
        String palindrome = "";
        for(int index = i+1-p[i]; index < i + p[i]; index++){
            palindrome += '#' == newStr[index] ? "" : newStr[index];
        }

        return palindrome;
    }
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值