情况分析: (1)当前点 i 不在回文右边界里:暴力扩充,无优化,直接扩 (2)当前点 i 在回文右边界里: L:左边界,R:右边界,C:中心点,i‘ :i 关于C的对称点 在回文半径数组中可以得到,i’ 的回文情况 (a ) i’ 的回文区域在L内部,则 i 的回文半径就是 i‘ 的回文半径 (b ) i’ 的回文区域有一部分已经在L外部,那么 i 的回文半径就是 i 到 R的部分 (c ) i‘ 的回文区域与L重合,i 的回文半径一定 >= i 到 R 的部分,需要对R 后的东西与R关于 i 的对称点 R’ 进行比较,判断回文区域
时间复杂度O(N),N为字符个数
代码实现
若使用情况分析的结果挨个判断实现代码太过繁琐
可以整合无需判断回文的部分,得到对应的回文半径后,跳过无需判断的部分后,剩余代码部分情况全部判断是否能扩张 (1)情况1,无需比较的部分就是自己回文半径从1开始 (2)情况2,a的情况是最小值,b与c都是基于回文右边界 - i,则比较preArr[ i‘ ] 与 R - i + 1的大小,取较小值就是不用判断的部分
public static int manacher(String str){
if (str == null || str.length() == 0){
return 0;
}
char[] chs = manacherString(str); //添加特殊字符
int[] preArr = new int[chs.length]; //回文半径数组
int mid = -1; //回文中心
int R = -1; //回文右边界
int max = Integer.MIN_VALUE; //最大回文长度
for (int i = 0; i < chs.length; i++){
//遍历字符数组
//不用判断的区域
//i <= R当前点i不在右边界,回文半径至少是 1
//i在右边界的两种情况:i'的回文在 L内,或者L外i
//通过对称点 i’ 或者 R - i中的最小值,就可判断时哪一种情况
preArr[i] = R > i ? Math.min(preArr[2 * mid - i], R - i + 1) : 1;
while (i + preArr[i] < chs.length && i - preArr[i] > -1) {
//不超过左右字符数组的情况下
//无论是哪一种情况都判断能否扩张,使得代码精简
if (chs[i + preArr[i]] == chs[i - preArr[i]]){
//满足扩展
preArr[i]++;
}else {
break;
}
}
if (i + preArr[i] > R){
//更新R,mid
R = i + preArr[i] - 1;
mid = i;
}
max = Math.max(max, preArr[i]);
}
//原字符串是扩充串回文半径 - 1
return max - 1;
}
public static char[] manacherString(String str) {
//添加特殊字符,使得回文字符的长度全部变成偶数
char[] strArr = str.toCharArray();
char[] chs = new char[str.length() * 2 + 1];
for (int i = 0, j = 0; i < chs.length; i++){
//偶数位置加入特殊字符
chs[i] = (i & 1) == 0 ? '#' : strArr[j++];
}
return chs;
}