Manacher算法
时间复杂度O(N) 快速求出字符串的最长回文子串的长度
abccba 最长为6
1124211 最长为7
暴力解法
以每一个字符为中心向两边扩,但是这种办法只能保证奇数的解决,无法计算到偶数的
解决方法为,在每两个字符中间加一个字符,头尾也加一个字符,
然后再以每个字符为中心往两边扩充,最后得出的结果都/2 (向下取整)最大的那个数字就是最长回文字串的长度了
时间复杂度为n方
最坏情况为每个字符都相同,前半部的字符需要找到左边界,后半部分字符需要找到右边界
Manacher 算法
回文半径
数一侧
回文直径
两次都算
回文半径数组
arr
到目前为止的回文最右边界(记做R) 与 中心 (记做C)
回文最右边界为当前遍历过的字符的最右边的字符位置,中心的意思是当前最右边界下的中心位置的字符
1x1x3x1x1 (x为辅助字符)
若当前字符为1 ,位置为0,则最右字符为位置0,中心为也为0
算法步骤
index 在 R外,暴力计算最长回文字串
index在R内:
index关于C位置的对称位置 index2 ,
求index 的最长回文字串需要看index2,
index2的最长回文串有三种情况
最长回文串在, 【r2-------C-------R】内
arr【index】=arr【index2】
最长回文串在, 【r2-------C-------R】外,超出一部分
arr【index】= R-index
最长回文串在, 【r2-------C-------R】内,刚好压线
arr【index】= arr【R-index】+ ?
?是因为不知道再往右扩充时是否还能构成回文串
public static int manacher(String s) {
if (s == null || s.length() == 0) {
return 0;
}
// "12132" -> "#1#2#1#3#2#"
char[] str = manacherString(s);
// 回文半径的大小
int[] pArr = new int[str.length];
int C = -1;
// 讲述中:R代表最右的扩成功的位置
// coding:最右的扩成功位置的,再下一个位置
int R = -1;
int max = Integer.MIN_VALUE;
for (int i = 0; i < str.length; i++) {
// 0 1 2
// R第一个违规的位置,
// i>= R 为 i在R外,至少不用验证的区域为1
// i位置扩出来的答案,i位置扩的区域,至少是多大。
// 2*c-i。是i的对称点
//
pArr[i] = R > i ? Math.min(pArr[2 * C - i], R - i) : 1;
// 不用验证的部分结束了,开始计算左右两边是否能够再扩充
while (i + pArr[i] < str.length && i - pArr[i] > -1) {
if (str[i + pArr[i]] == str[i - pArr[i]])
pArr[i]++;
else {
break;
}
}
//更新 r 和 c
if (i + pArr[i] > R) {
R = i + pArr[i];
C = i;
}
max = Math.max(max, pArr[i]);
}
// 因为求的是半径,所以是max-1,若求的是直径,那就是 max/2
return max - 1;
}
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;
}
添加字符变成回文串
在一个字符串str中,在末尾添加多少个字符,可以使得整个字符串为回文字符串?
题目在求,必须包含最后一个字符的情况下,最长回文串有多长
当最后一个回文子串包含最后一个字符时,
将最后一个字符前面 没有被回文子串包住的字符,逆序填到字符串末尾
abc 12321
abc 【12321】 cba
查找最左的,回文半径能把最后一个字符包住的位置,
然后找到左边界,把左边界到字符开头的字符逆序补充到字符串尾部
public static String shortestEnd(String s) {
if (s == null || s.length() == 0) {
return null;
}
char[] str = manacherString(s);
int[] pArr = new int[str.length];
int C = -1;
int R = -1;
int maxContainsEnd = -1;
for (int i = 0; i != str.length; i++) {
pArr[i] = R > i ? Math.min(pArr[2 * C - i], R - i) : 1;
while (i + pArr[i] < str.length && i - pArr[i] > -1) {
if (str[i + pArr[i]] == str[i - pArr[i]])
pArr[i]++;
else {
break;
}
}
if (i + pArr[i] > R) {
R = i + pArr[i];
C = i;
}
if (R == str.length) {
maxContainsEnd = pArr[i];
break;
}
}
//maxContainsEnd - 1 为包含最后一个字符的最长回文长度
char[] res = new char[s.length() - (maxContainsEnd - 1)];
for (int i = 0; i < res.length; i++) {
res[res.length - 1 - i] = str[i * 2 + 1];
}
return String.valueOf(res);
}
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;
}