Manacher算法

参考:https://blog.csdn.net/weixin_42373330/article/details/82118694

水平有限,只是记录下自己的学习过程

马拉车算法用于求最长回文子串,时间复杂度O(n)。

先了解一下p[i],id,mx的含义。
p[i]表示以Str[i]为中心的最长回文子串的边界到Str[i]的距离(包括Str[i])
算法用p[i]表示第i个字符到mx的距离(包括i);
id记录出现的最长回文子串的中心位置;
mx记录出现的最长回文子串的最右边界的右边一个位置,mx=id+p[id]。

ababc为例,要先对字符串进行预处理(为了方便处理):用"#“插进字符串中,包括头和尾,再在头部插入“$”,尾部插入”&",最后变成

**$#a#b#a#b#c#&**

假如i=4,即第一个b,那么最长回文子串是#a#b#a#,p[i]=4.本次操作结束后的id=4,mx=8.

P数组的计算

算法的精髓就在于P数组,那么如何计算呢?
首先从左到右依次计算,但当计算P[i]时,P[j](j<i)已经计算完毕,所以要充分利用这个已知条件来简化计算。
根据i和mx的位置关系,可以分成两种情况

  1. i>=mx:这种情况没有已知条件能够利用,所以令p[i]=1
  2. i<mx:
    令j=2*id-i,即i和j关于id对称。
    由于是从左往右计算p[i],故此时p[ j ]已经计算好了,现在要做的事情是如何利用已经算好的p[ j ] 来更新p[ i ],从而提高效率。

图中,假设i-6~i是以i-3为中心的最长回文子串p[i-3]=4,此时id=i-3,mx=id+p[id]=i+1,
在这里插入图片描述
现在计算p[i]时,对i和mx的比较需要分两种情况:

一、i>=mx

i在mx的右边,例如遍历到 i+2,这个时候没有太多的已知信息能够利用,无法做出假设,只能先令p[i+2]=1,然后再做后续匹配

二、i<mx

令j=2*id-mx,str[j]是str[i]关于id的对称点
由于p[j]已知,现在就需要考虑如何利用p[j]简化计算从而提高效率。

p[j]的左边界存在两种情况:在mx的对称点的左边或右边。

如果在mx的对称点的左边,那么超出部分关于id不对称,也就是下图的1和4不相等
如果在mx的对称点的右边,也就是被mx完全包围,那么这是p[i]=p[j]

因此,将 mx-i 和 p[j] 进行比较分成两种情况:
1、p[j]>=mx-i
说明以 j 为中心的回文字串有部分超出了以id为中心的回文子串,而超出的部分(下图中的1部分)关于id的对称部分(下图中4部分)必定>=mx,,可知1,2,3部分都对应相等,而之前讲过mx与mx的对称点指向的字符是不相等的,说明1与4部分不对应相等,所以我们所能确保的是 p[i] 至少为mx-i
在这里插入图片描述

2、p[j]<mx-i
以第j个字符为中心的回文子串全包含在以第id个字符为中心的回文子串中,基于对称性可知,即下图中颜色相同的对称相等,所以p[i]=p[j]=p[2*id - i],也不需要进行延伸判断。
在这里插入图片描述

代码过程:

  1. 初始化:mx=0,i=1(“$”不考虑)
  2. 计算p数组
if (mx <= i) {
    p[i] = 1;
} else if (mx - i > p[2 * mid - i]) {
//  这种情况不需要继续左右延伸
    p[i] = p[2 * mid - i];
    needExtend = false;
} else
//  推算出来已知的回文子串的长度
   p[i] = mx - i;
  1. 进入while循环,从当前已知回文子串范围开始,即p[i]范围外,判断左右是否相等,如果相等p[i]++。
if (needExtend)
   while (str.charAt(i - p[i]) == str.charAt(i + p[i]))
     p[i]++;
  1. 更新变量

完整代码

//预处理
	private String preHandleString(String s) {
        StringBuilder sb = new StringBuilder();
        //在首位各加一个不同的字符 防止遍历出界
        sb.append("%#");
        for (char c : s.toCharArray()) {
            sb.append(c).append("#");
        }
        sb.append("$");
        return sb.toString();
    }

    private String manacher(String s) {
        String str = preHandleString(s);

        int longest = 0, center = 0;
        int mx = 0, mid = 0;
        int len = str.length();
        int[] p = new int[len];
        for (int i = 1; i < len - 1; i++) {
            boolean needExtend = true;
            if (mx <= i) {
                p[i] = 1;
            } else if (mx - i > p[2 * mid - i]) {
//                这种情况不需要继续左右延伸
                p[i] = p[2 * mid - i];
                needExtend = false;
            } else
//                推算出来已知的回文子串的长度
                p[i] = mx - i;

            if (needExtend)
                while (str.charAt(i - p[i]) == str.charAt(i + p[i]))
                    p[i]++;

            mid = i;
            mx = i + p[i];
            if (p[i] > longest) {
                longest = p[i];
                center = i;
            }
        }
        StringBuilder out = new StringBuilder();
        for (int i = center - longest + 2; i < center + longest - 1; i += 2) {
            out.append(str.charAt(i));
        }
        return out.toString();
    }
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值