Manacher算法

问题原型:一个字符串找到最长回文字串
例如 121 1221 是回文
思路:暴力破解,需要解决奇回文和偶回文的问题
奇回文,从第 i 个位置开始,以 i 为中心,向两边扩张,如果发现 i 左右相等,那么回文数量 +2
例如 abcba
从 i = 1 开始, 无法向左扩展,因此回文长度为 1
从 i = 2 开始,向左右扩展,回文长度为 3
从 i = 3 开始,向左右扩展,回文长度为 3,再次扩展,回文长度为 5
偶回文,不能像奇回文一样的思路,例如 1221,不能处理单个字符

通用方法(无论奇偶):处理字符串
用占位符 # 占位,求出最长回文值,再除以 2 就是最长回文

-------- Manacher---------
回文直径:以一个中心开始,能扩多大的直径
回文半径:回文直径 / 2
准备一个数组,arr[],记录了每个位置的回文半径,我们试图寻找,利用前面的回文半径记录,能否加速寻找下一个回文半径的速度
最右回文右边界 R:例如 #2#2#1#……
一开始,最右回文右边界为 -1
当从 i=0 位置开始扩,只能扩到自己,得到的 最右回文右边界 是这种形式的:#)2#2#1#……,最右回文右边界的位置是 0,回文半径 R=0
当从 i=1 位置开始扩,能扩充左右各一个位置,得到最右回文右边界是这种形式的:#2#)2#1#……,最右回文右边界的位置是 3,回文半径 R=1
……
当从 i=3 位置开始扩,得到 #2#2#1#)……,最右回文右边界的位置是 6,R=3
回文右边界 L:对应 最右回文右边界
最右回文右边界中心 c :得到最右回文右边界的中心是哪里,就是最右回文右边界中心
如果有多个位置能到达同样的最右回文右边界,那么中心是 第一次到达中心 的位置

Manacher 思路:
可能性 1:如果当前位置 i 不在最右回文右边界里面,用上面的暴力扩充
例如:#1#2#1#
一开始 最右回文右边界是-1,且当前位置 i 是 0,# 不在界内,用暴力扩充,最右回文右边界是1
然后,当前位置 i = 1,还是不在界内,再次暴力扩,最右回文右边界是3
当前位置 i = 2,在界内,来到下面的几种可能性
可能性 2:
当前位置 i 在 最右回文右边界 界内
找到 i 位置的 对称点 i’,并根据 arr[] 得到 i’ 的回文半径
且 i’ 的回文半径在 L 和 R 的内部
那么 i 的回文半径和 i’一样,不用再次求。例如:

可能性 3:
当前位置 i 在 最右回文右边界 界内
找到 i 位置的 对称点 i’,并根据 arr[] 得到 i’ 的回文半径
且 i’ 的回文半径不在 L 和 R 的内部,且左边超出
那么,i 的回文半径 就是 i-R

可能性 4:
当前位置 i 在 最右回文右边界 界内
找到 i 位置的 对称点 i’,并根据 arr[] 得到 i’ 的回文半径
且 i’ 的回文半径正好与 左边界 L 重合
那么,i 的回文半径,是 i-R 再加上一个数,这个数需要自己暴力扩。也就是说 i’ 与 L 重合的时候,i-R 的这些数据半径不需要再验证了,但是 R 之外的数据 需要自己再验

回文半径情况总结:
可能性 1:i 不在 R 内,暴力扩充
可能性 2:i 在 R 内
可能性 2.1:i’ 回文在 (L, R)区间内,且不压线,i 的回文半径和 i’一致
可能性 2.2:i’ 回文超出 L,i 的回文半径 是 i-R
可能性 2.3:i’ 回文半径,压在 L 上,和 L 重合,那么 i 的回文半径是 i-R 加上某个数,这个数需要从 R+1 的位置继续往后验证

复杂度分析
R 只往右推
如果 i 在 R 外,R 一直会增大
2.1 2.2 复杂度都是 O(1)
1 和 2.3 由于 R 只会往右推,因此,复杂度为O( N)
因此,Manacher 算法复杂度为 O(N)

package kmp和Manacher;

public class Manacher {

public static int manacher(String str) {
	if(str == null || str.length() == 0) return 0;
	
	//存储的数组
	char[] charArr = manacherString(str);
	//回文半径
			int[] pArr = new int[charArr.length];
	//回文中心
	int C = -1;
	//最右回文半径
	int R = -1;
	int max = Integer.MIN_VALUE;
	
	for(int i=0;i!=charArr.length;i++) {
		
		if(R>i) {
			//i 位置 在 最右回文半径边界里面
			// i_   为 i 镜像位置
			int i_ = 2 * C - i;
			// i_ 的回文半径
			int i_R = pArr[i_];
			// 右边界到 i的距离
			int d = R - i;
			pArr[i] = Math.min(i_R, d);
		} else {
			//i 位置 不在最右回文半径里面
			pArr[i] = 1;
		}
		
		//pArr[i] = R > i?Math.min(pArr[2*C-i], R-i):1;
		
		while(i+pArr[i]<charArr.length && i-pArr[i]>-1) {
			if(charArr[i+pArr[i]] == charArr[i-pArr[i]]) {
				pArr[i]++;
			} else {
				break;
			}
		}
		if(i + pArr[i] > R) {
			R = i + pArr[i];
			C = i;
		}
		max = Math.max(max, pArr[i]);
	}
	return max - 1;
}

private static char[] manacherString(String str) {
	//将字符串处理一下
	char[] charArr = str.toCharArray();
	char[] res = new char[str.length() * 2 + 1];
	int index = 0;
	for(int i=0;i!=res.length;i++) {
		res[i] = (i & 1)==0?'#':charArr[index++];
	}
	return res;
}

}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值