Manacher算法: 在一个字符串中找到最长的回文字串
例如:abc12321def 最长回文字串为12321
传统思路是遍历每个字符,以此字符为中心向两边扩,看两边的字符是否相等,如果是回文串的长度是奇数的话,可以往两边扩,如果是偶数的话(例如1221)则不行!因此需要加特殊字符,例如(1221—>改为#1#2#2#1#)而且传统思路的时间复杂度比较高。如原始数据为N,加上特殊字符后字符串长度为2N,最差情况每个字符都相等,因此每个字符都扩一次,时间复杂度为O(N*2)
而manacher算法将时间复杂度优化到O(N)
如何做:
第一:同样需要将字符串加上特殊字符
第二:回文半径(abcba的回文半径为3(加上中心点))
回文半径数组 dp[...],,,,,dp[i]表示第i个字符的回文半径
第三:最右回文边界 例如#1#2#3#2#1#
下标C 012345678910
边界R 0 2 4 10 //下标为0是最右回文边界为0,下标为2时最右回文边界为4
为什么manacher算法比传统方法快,因为在找回文串的时候做了加速,,,
|
例如:a b c d e d c b a
i' C i R //C为中心 ,,,R为最右回文边界 ,找i的回文半径时,因为i'和i对称,因此i的回文半径
可能等于i'的回文半径,,,因此有两种大情况,三种小情况
(1)最右回文边界没有括住i的字符(暴力扩)
(2)i在R里面
1. i的回文半径一定在L~R内部,,,(L~R)为最右回文半径中心的回文直径 ,,,如果i'的回文半径小在L~R内,则i也在L~R内
2. i'的回文区域没完全在L~R内
例如 ab cdcbatttabcdc k
L i' C i R
i'的回文半径没完全在L~R中 设i'的L和R为a,d ,,,d关于C的对称点为c,,,b为最右回文边界下一位字符
则a!=b a==d d==c 所以b!=c 则i的回文半径等于(R-i)
3. a b c b a t t t a b c b a ?(t||k)
i' x C i 如果i'的回文半径压线,则如果R后一位等于x位的则能扩,不能则不能扩(如等于t能扩,等于k不能扩)
时间复杂度 :(1)i不在R内暴力扩
(2)1. i'回文半径在L~R内 i则知道 O(1)
(3)2. i'回文半径部分在外 i则知道 O(1)
3. i'回文半径压线 判断扩不扩 //(1)和(3).3都扩的话最多扩N个,则时间复杂度为O(N)
代码:
public class Manacher {
public 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;
}
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;
}
public static int maxLcpsLength(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++) {
pArr[i] = R > i ? Math.min(pArr[2 * C - i], R - i) : 1;//如果i在最右回文边界右边暴力扩,否则基础回文半径为(R-i)
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]); //#a#b#a#
}
return max - 1;
}
public static void main(String[] args) {
String str1 = "abc1234321ab";
System.out.println(maxLcpsLength(str1));
}
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++) {
pArr[i] = R > i ? Math.min(pArr[2 * C - i], R - i) : 1;//如果i在最右回文边界右边暴力扩,否则基础回文半径为(R-i)
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]); //#a#b#a#
}
return max - 1;
}
public static void main(String[] args) {
String str1 = "abc1234321ab";
System.out.println(maxLcpsLength(str1));
}
}