Manacher算法

对于一个字符串,请设计一个高效算法,计算其中最长回文子串的长度。

给定字符串A以及它的长度n,请返回最长回文子串的长度。

测试样例:
"abc1234321ab",12
返回:7

1. Manacher算法解决什么问题问题:求一个字符串的最长回文子串

当然暴力的解法就是:遍历每个字符,以其为中心往外扩,复杂度N^2


2. 步骤

(1)预处理,为了把像1221这样的情况也放到一起处理,先构造一个新的字符串#1#2#2#1#,这样就不会出现回文中心的index为2.5这样的情况了

(2)定义3个主要的变量,并初始化

int[] radius:radius[i]表示i位置对于的回文半径

int pr: 遍历i的过程中可以匹配到的最右边的位置

int index:与pr对于的中心索引

(3)遍历i,在遍历的同时更新3个主要的变量,遍历的时候有4种情况需要讨论,其中2种情况不需要扩,一种情况需要扩但是已经有一定的基础,最后一种情况从0开始往外扩

<1> i对称过去的索引被index的回文范围完全包住,比如:5#1#2#1#3#1#2#1#5,这种情况是不需要要扩的,radius[i]就是radius[i_mirror]

<2> 对称过去的左边落在index的回文范围外面,比如:#a#b#a#c#a#b#d#,这种情况也是不需要要扩的,radius[i]就是index对应的右大到i的距离(可证明)

<3>对称过去的左边正好落在index的最左边,比如:#d#a#b#a#c#a#b#a#c#,这种情况需要扩(因为可能还可以扩得更宽),但是已经有了一定的基础

<4>i落在pr的右边,肯定要从0开始开始扩



3. 可以看到算法的加速过程在于:利用了之前匹配过的信息!就像KMP利用已经结算过的next数组一样!

通过对pr变量的分析,可以知道每次扩都会引起pr的增大,pr和扩是同步的,扩大了,下次不需要扩的可能性就更大了,而pr只是从0遍历一遍到了2*n,所以复杂度O(N)


实际代码:

public class Palindrome {
    public int getLongestPalindrome(String A, int n) {
    	// 为了把1221这样的情况也放到一起处理,先构造一个新的字符串
    	int[] cs = new int[A.length()*2+1];
    	cs[0] = '#';
    	for(int i=1; i<cs.length; i+=2) {
    		cs[i] = A.charAt(i/2);
    		cs[i+1] = '#';
    	}
    	
    	int[] radius = new int[cs.length];		// i位置对于的半径
    	int pr = 0;								// 当前匹配到的最右边的位置
    	int index = 0;							// 与pr对于的中心索引
    	
    	// 虽然for里面有while,但是for中的i是遍历一遍,while里面的pr也是遍历一遍,所以复杂度O(n)
    	for(int i=0; i<cs.length; i++) {
    		if(i < pr) {
    			int mir = 2*index-i;				
    			int mir_left = mir-radius[mir];
    			int idx_left = index-radius[index];
    			
    			// 1. 对称过去的被index的范围完全包住
    			if(mir_left > idx_left)
    				radius[i] = radius[mir];
    				
    			// 2. 对称过去的左边落在index的回文范围外面
    			if(mir_left < idx_left)
    				radius[i] = mir - idx_left;
    			
    			// 3. 对称过去的左边正好落在index的最左边
    			if(mir_left == idx_left) {
    				while(pr<cs.length && 2*i-pr>=0 && cs[pr]==cs[2*i-pr])	pr++;
    				radius[i] = pr-i;
    				index = i;
    			}
    		} else {
    			
    			// 4. i落在pr的右边,肯定要开始扩
    			while(pr<cs.length && 2*i-pr>=0 && cs[pr]==cs[2*i-pr])	pr++;
    			radius[i] = pr-i;
				index = i;
    		}
    	}
    	
    	int max = 1;
    	for(int i : radius)	max = Math.max(max, i);
    	return max-1;
    }
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值