Manacher算法——最长回文子串

Manacher——最长回文子串

1. Manacher是干什么的?

Manacher算法是一个用来查找一个字符串中的最长回文子串(不是最长回文序列)的线性算法。它的优点就是把时间复杂度为O(n^2)的暴力算法优化到了O(n)。

2. 原始方法

在没有学会Manacher算法之前,查找最长回文子串的方法就是纯暴力的匹配。根据每一个当前字符,向左右扩展。

  • 先不去判断对与错,单从这种算法上来讲,就已经是O(n^2)。
  • 其次,如果是偶回文就不是那么好判断了,比如abba,不能过滤出回文子串。

3. Manacher含义

Manacher和KMP是一个意思,都是基于暴力匹配做了一些加速操作,有那么点DP的意思。但是Manacher判断回文子串的时候还做一些预处理。

3.1. 预处理

预处理主要是为了判断偶回文,以前的abba无法处理,但是经过Manacher的预处理后,就会变得格外的不同。

	private char[] process(char[] chs){
		//重新声明一个字符数组
        //length == chs.length*2 + 1 ?
        char[] c = new char[chs.length*2 + 1] ;
        int index = 0 ;
        for (int i=0 ; i<c.length ; i++){
            c[i] = (i&1) == 0 ? '#' : chs[index++] ;
        }

        return c ;
    }
  • char[] c:新创建的数组
  • length == chs.length*2+1:需要将abba处理成 #a#b#b#a# ,为什么要处理成这个亚子?是因为要给偶回文找一个对称轴。

大致的逻辑就是这个样子,预处理这块代码很好懂。

3.2. Manacher核心部分

先上代码,后面讲解

	public int manacher(String str){
        if (str == null || str.length() == 0){
            return 0 ;
        }
        char[] chs = process(str.toCharArray()) ;
        int l = chs.length ;
        //下面介绍的四个属性
        int index = -1 ;
        int pR = -1 ;
        int[] pArr = new int[l] ;
        int max = Integer.MIN_VALUE ;

        for (int i=0 ; i<l ; i++){
            //更新当前位置的初始化半径区域
            pArr[i] = pR > i ? Math.min(pArr[2*index-i] , pR - i) : 1 ;
			//左右扩充
            while(i + pArr[i] < l && i - pArr[i] > -1){
                if (chs[i+pArr[i]] == chs[i-pArr[i]]){
                    pArr[i]++ ;
                }else{
                    break ;
                }
            }
            //更新中心点和最右边界
            if (i + pArr[i] > pR){
                pR = i + pArr[i] ;
                index = i ;
            }
			//更新max
            max = max < pArr[i] ? pArr[i] : max ;
        }

        return max - 1 ;
    }

四个要素

  • index:中心点位置
  • pArr[]:每个位置的回文半径
  • pR:最右可到达的回文半径,和index相对应
  • max:回文半径最大值

有了这四个属性,理解Manacher还会远吗?

3.3. 大体流程

  1. 处理原始数据
  2. 第一种情况,i > pR 暴力扩
  3. 第二种情况,i < pR && pArr[i] + i < pR 也就是当前位置的右半径没有超过最右半径
  4. 第三种情况,i < pR && pArr[i] + i > pR 也就是当前位置的右半径超过最右半径

按照流程我们一步一步的走

首先是index=-1 pR=-1 i=0,为了方便,我先把pArr数组写好了。

  • i = 0 时,说明i大于pR,需要暴力扩,pArr[0] = 1 ,

基本上就是按照三种情况进行的。

我们进行跳步

  • 当 i = 5 时,状态如下图,可以看出index就是pR的中心点。i对应i`的半径2,说明#a#不需要再遍历了。接着就是向外扩了。可以左右扩到#b#a#b#,所以pArr[5]就等于4。然后更新index和pR,最后更新max。

3.4. 扩展的条件

扩展的条件就是while循环的条件,i + pArr[i] < l && i - pArr[i] > -1

  • 第一个条件很好理解,就是当前位置i + 他的半径要小于整个数组的长度。
  • 第二个条件可以这么看,i > pArr[i] -1 这个是保证左边界不出界。

3.5. 加速运动

加速运动体现在哪里,就是以index为中心点的 i 的对称点 **i ` ** 。

如果 i 在index 和 pR 中间,那么 i 的对称点就是 i’ 。所以将i‘ 的信息直接拿过来用就可以。但是需要做一些处理,也就是 min(pArr[2*index-i] , pR - i) 。为什么?

因为 在以index为中心,pR为半径的区域内,可以保证 i 和 i’ 是相对应的。如果大于pR的范围,是不保证pArr对应的。

4. 总结

Manacher其实有一部分DP的思想在里面,但是还是基于暴力破解。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值