[LeetCode][M0005]最长回文子串(Java)(马拉车(Manacher)算法)

题目描述:

给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。

示例 1:

输入: “babad”
输出: “bab”
注意: “aba” 也是一个有效答案。

示例 2:

输入: “cbbd”
输出: “bb”

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/longest-palindromic-substring
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。


解题思路

我估么着看到马拉车算法部分人会和我一样一脸懵逼,听都没听过,那我先粗略说下马拉车。

分步来解决问题:

①奇偶个数
回文字符串比较恶心的就是奇数偶数了,那么如果在每个字符前后之间插入一个一样特殊字符,那么以特殊字符为中心的回文就是偶数个,否则就是奇数个。

举例:“babaad”
我们插入“#”,就变成了“#b [#a#b #a#]a# d#”。
看着有点乱,高亮部分是偶数个,括号部分是奇数个,这样一来我们就无须在意奇偶个数问题了,因为全变成了奇数个。

②边界问题
这样我们还是需要判断边界情况,不如再加上边界。

举例:“#b#a#b#a#a#d#”
加上前后边界,变成“$#b#a#b#a#a#d#*”。
因为 $ 和 *都不同于任何一个字符;假设判断第一个“#”字符,他的前后字符“ $ ”和“ b ”不同会直接退出循环判断。

③简化思想(马拉车核心)
试想一下,现在有个字符串“1232145412321”,整个字符串关于5对称,第一个3在5的对称范围内有一个“12321”的小对称,那么对应的5的右边的3必然也有“12321”的小对称。
这样不用每次都从零开始,循环判断其左右是否相同,可以直接获取对称位置[5左边的3]的对称半径。需要注意的不能超过对称范围[5的半径是7]。

举例:“$#a#b#a#b#a#b#*”
创建一个记录对称半径的数组p,一个记录最远对称范围的值range,一个记录拥有最远对称的index值idx。

  1. 从第一个#开始,while(i-p[i] == i+p[i])循环可知p[1] = 1,只有自己一个,此时idx更新为1,range更新为1;
  2. 到第二个字符“a”,因为超出了idx的对称范围 (0,2),所以while循环得p[2] = 2,idx = 2,range= 2;因为2+2 = 4,比1+1=2更远。
  3. 第三个字符“#”,因为在idx的对称范围内(0,4),所以p[3] = min(p[2*idx-i],idx+range-i);之所以取最小,是担心前边出现的那个字符对称半径超出了idx的对称范围。不懂的话下边会说到。
  4. 依次类推,八位置的字符“b”的p[8] = 6,它的对称范围是(2,14),即“#b#a#b#a#b#”。
    那么我们现在到了12位置的“b”,它相对于这个8位置的“b”的对称位置为4位置的“b”,我们可以知道p[4] = 4,即“#a#b#a#”,但是p[12]肯定不是4,越界了。
    这是因为8位置的对称范围是(2,14),而4位置是(0,8),0小于8,超出了对称范围,所以12位置取min(p[2*idx-i],idx+range-i);

大致就是这么个流程了,简单叙说一番。

反思错误

①还是在判断边界的时候会出错,p数组存的对称半径是包括自己的!
②StringBuilder没有记住,写成了StringBuild,还用错了方法,应该是append,我写的add。

Java代码

/**
 * len 字符串的长度
 * cslen 新字符数组长度
 * cs 新字符数组
 * idx 最远范围的index值
 * range idx的对称半径
 * maxi 最长回文的index值
 * maxm maxi的对称半径
 * p 记录对称半径
 */

class Solution {
    public String longestPalindrome(String s) {
        
        int len = s.length();
        int cslen = 2*len+3;
        
        //初始化
        char[] cs = new char[cslen];
        cs[0] = '$';
        cs[1] = '#';
        cs[cslen-1] = '.';
        
        for(int i=2,j=0;i<cslen-1;++i,++j){
            cs[i] = s.charAt(j);
            cs[++i] = '#';
        }
//        for(int i=0;i<cslen;++i)
//        	System.out.print(cs[i]);
        
        //马拉车
        int idx = 0;
        int range = 0;
        int maxi = 0;
        int maxm = 0;
        int[] p = new int[cslen];
        
        for(int i=1;i<cslen-1;++i){
            
            //i在idx对称范围内
            if(i<idx+range)
                p[i] = Math.min(p[2*idx-i],idx+range-i);
            
//            System.out.println(i+" "+idx+" "+range+" "+p[i]);
            
            //循环判断左右是否对称
            while(cs[i-p[i]] == cs[i+p[i]]){
                ++p[i];
            }
            
            //记录最长的回文串index和半径
            if(maxm<p[i]){
                maxi = i;
                maxm = p[i];
            }
            
            //当出现更远的位置时,更新
            if(i+p[i]>idx+range){
                idx = i;
                range = p[i];
            }
                
            
        }
        
        StringBuilder sb = new StringBuilder();
        for(int i=maxi-maxm+1;i<maxi+maxm;++i){
            if(cs[i] != '#')
                sb.append(cs[i]);
        }
        
        return sb.toString();
        
    }
    
}

提交结果

执行结果:通过
显示详情
执行用时 :5 ms, 在所有 java 提交中击败了97.68% 的用户
内存消耗 :36.1 MB, 在所有 java 提交中击败了91.28% 的用户

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值