LeetCode做题记录 06

最长回文子串

点击这里查看原题

题目描述

给定一个字符串s,输出它最长的回文子串
在这里插入图片描述

Manacher(马拉车)算法

其实本篇文章注重想讲讲这个Manacher算法。其实这个题目我们容易想到,遍历字符串s的每一个字符,假设遍历到i,那么就把这个字符当做回文中心,即回文串的对称轴,然后从两边开始扩张,直到不是回文的为止,记录下来最大长度的起点与终点即可,这种算法叫中心扩展法。中心扩展法还得分回文中心长度为1和2两种情况考虑,因此时间复杂度为O(n²)。

而Manacher算法运用了动态规划+中心扩展法优化了上述算法,充分利用了回文串的对称性。

首先,我们得把字符串的每一个空隙都加上一个不可能存在的字符,比如说’#’,如图在这里插入图片描述
为什么要这样做呢?原因是要把字符串长度变为奇数,这样就可以不用讨论回文中心的长度是1是2的情况了。证明:假设len为原字符串长度,一共有len-1个空隙,加上头尾两个符号,因此新字符串长度为 len + len -1 + 2 = 2*len + 1是一个奇数

同时,我们可以发现,在新字符串中,以某一个字符为回文中心的
(最大回文子串长度 - 1)/2 = 原来字符串中以该字符为回文中心的回文子串长度 = 从该字符出发走到最右端所需步数(即回文半径)

因此我们把所有以i为回文中心的回文半径求出来即可。

为了下面能解释的更清楚,先说明几个定义:
1. 回文半径r:从回文中心出发,走到该回文串最右端所需距离。
2. f(i):i为回文中心,f(i)则是以i为回文中心的最大回文半径。
3. maxRight:某个回文中心的最大回文子串的最右端的下标。

本算法最根本的思路就是通过动态规划和中心扩展法求出每一个f(i),
我们可以考虑以下两种情况:

当 i >= maxRight

在这里插入图片描述
(注意,图中的s是加了符号填充之后的字符串)
当 i > maxRight 时,我们只能根据中心扩展法法来求出以i为回文中心的最大回文子串长度。

当 i < maxRight

这种条件下我们就要充分利用回文串的对称性来求出f(i)
我们可以分三种情况讨论:
注意:以下的mirror都是i关于center的对称点

①f(mirror) < maxRight - i
在这里插入图片描述

这种情况可以这样描述:f(mirror)要小于mirror到左端点的距离,而根据对称性,mirror到左端点的距离等于i到maxRight的距离,因此有: f(mirror) < maxRight - i
那么f(i)该怎么求呢?我们很容易想到,在i到maxRight之间的每一个字符与i到center之间的每一个字符都分别与mirror到左端点之间的每一个字符和mirror到center之间的每一个字符对称。 因此f(i) = f(mirror)

②f(mirror) = maxRight - i
在这里插入图片描述
当这种情况时,我们最多只能确认f(i) >= maxRight - i的,因为根据对称性,很容易就得出来,以i为中心,半径为maxRighht - i 的子串必定回文,但是我们无法确认,扩充之后是否还是回文串,我们只能这样描述:当f(mirror) = maxRight - i ,f[i]至少等于maxRight - i,然后再以maxRight为中心进行中心扩展

③f(mirror) > maxRight - i
在这里插入图片描述
在②中,我们很容易得出,f(i) = maxRight - i ,那么为什么f(i) 有可能等于 f(mirror)吗,答案是不可能,假设f(i) = f(mirror),也就是说,以center为中心的最大子串的最右端应该大于maxRight,因此矛盾。所以,f(i) = maxRight - i

综上所述:
当i>=maxRight ,只能通过中心扩展法得出f(i)
当i<maxRight,f(i)>= min{ f(mirror),maxRight - i },再尝试中心扩展法

代码

class Solution {
    public String longestPalindrome(String s) {
        StringBuilder sb = new StringBuilder();
        int n = s.length();
        for(int i=0;i<n;i++){
            sb.append('#');
            sb.append(s.charAt(i));
        }
        sb.append('#');
        String newStr = sb.toString();
        int newLength = newStr.length();
        int[] r = new int[newLength];
        int start = 0;
        int center = 0,maxRight = 0;
        int maxLen = 1;
        for(int i=0;i<newLength;i++){
            if(i<maxRight){
                int mirror = 2*center - i;
                f[i] = Math.min(f[mirror],maxRight - i);
            }
            //尝试进行中心扩展法
            int left = i - f[i] - 1;
            int right = i + f[i] + 1;
            while(left>=0 && right<newLength && newStr.charAt(left) == newStr.charAt(right)){
                --left;
                ++right;
                f[i]++;
            }
            //如果扩展完后发现此时最右端的下标比maxRight大,就要重置
            if(i + f[i] > maxRight) {
                maxRight = i + f[i];
                center = i;
            }
			//记录最大长度
            if(r[i] > maxLen){
                maxLen = f[i];
                //这个起点的计算方法读者自行证明吧
                start = (i - maxLen)/2;
            }
        }
        
        return s.substring(start,start + maxLen);
    }   
        
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值