问题原型:一个字符串找到最长回文字串
例如 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;
}
}