目录
Manacher算法的介绍
Manacher算法解决的问题是在字符串str中,最长回文子串的长度如何求解?如何做到时间复杂度O(N)完成?
对于这个问题的经典解法是,首先将字符串的每两个字符之间都加入一个辅助字符,同时开头和结尾位置也加入。对这个新的字符串计算它的最长回文子串,从第一个字符开始,以自身作为对称轴往左右两边扩充,如果左右两边的字符相同,继续扩充,如果不同,停止扩充。然后记录它的回文子串长度,继续求解其它字符,直到最后一个字符求解完毕。将这时的字符串的每个字符的回文子串长度进行比较,选出最长的然后除以2取整得到的就是原字符串的最长回文子串长度。但是采用这种方式的话,时间复杂度为,而Manacher算法就可以在时间复杂度
内完成这个操作。
回文半径与回文直径的详解
回文子串的长度叫做回文直径,根据回文直径的长度得到回文半径。同时记录最大回文右边界,也就是回文子串的最右侧位置。
Manacher算法的详解
Manacher算法利用的也是经典算法的操作思想,只是在步骤上进行了加速和优化。利用Manacher算法对字符串的最大回文子串长度的求解,实则是在求解处理后字符串的最大回文半径,对每一个字符的回文半径进行求解,然后得出最大的回文半径减去1即可得到原字符串的最大回文子串长度。
首先分为两种大的情况,第一种大的情况就是要求的字符的位置在最大回文右边界外面,那么此时只能利用暴力尝试的方式往外扩充。
第二种大的情况就是要求的字符的位置在最大回文右边界里面,那么第二种大的情况又可以分为三种小的情况,我们求解时利用的变量有最大回文右边界,记为R,整个回文子串的左边界记为L,中心点记为C,i位置关于中心点C的对称点记为i’。第一种小情况是,i位置的对称点i’的回文子串在L..R内,那么此时i位置的回文子串的长度就是i’位置的回文子串的长度,因为它们是回文的,关于中心点对称。第二种小的情况是,i’的回文子串位于L..R边界位置,那么此时i位置的回文子串在i’回文子串长度的基础上进行外扩。第三种小的情况是,i'的回文子串超出L..R,那么此时i位置的回文子串的长度就是R-i,因为首先i和i'是对称的,那么在区域范围内的回文子串的长度是相同的,而L..R的范围之所以没有继续外扩,是因为R右面的字符和L左边的字符不同,所以无法外扩,所以i位置的回文子串也无法外扩。
对于这个算法的时间复杂度进行分析,时间复杂度为。
public static char[] manacherString(String str) {//将字符串变成处理后的加上特殊字符的数组
char[] charArr = str.toCharArray();
char[] res = new char[str.length() * 2 + 1];//处理串的长度等于原字符串长度的2倍+1
int index = 0;
for (int i = 0; i != res.length; i++) {
res[i] = (i & 1) == 0 ? '#' : charArr[index++];
}
return res;
}
//对于这个算法的时间复杂度进行分析为O(N)
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;//回文右边界再往右一个位置,也就是最右的有效区到了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外面,那么这时候需要暴力尝试,至少不用检验的区域只有自己,也就是1,而如果i在R内,那么分为三种情况,当i的对称点i’的回文区域在L..R内,那么此时不用检验的回文区域就是i’的最大回文串长度,也就是pArr[2*C-i];当i的对称点i’的回文区域正好在边界位置,那么此时不用检验的区域为R-i;而如果i’的回文区域超出了L..R区域,那么此时i不用检验的区域也是R-L。
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;//处理串和原原字符串的关系就是,处理串的半径的长度-1就是原字符串最大回文子串的长度
}