回文:就是正着读和反着读一样
- 举例:
- ABCECBADE的回文子串就是就是:ABCECBA
- ABBABCF的子串就是:ABBA
回文问题的经典解法
- 加#:为了解决奇数回文字符串
- 从每个位置的从左从右对比:如果一遍直接越界限:即为1,如果有值的情况下越界,则不进行累加
- 如果一样,则累加上左右两边即:1+2
- 还是拿上面的例子来举:#A#B#C#E#C#B#A#D#E#
- #A#B#C#E#C#B#A#
- 一样的长度为15 :15/2(除去#的长度)= 7
- 问题来了:加的这个字符有没有要求说是原来的字符串中没出现过的
- 解:不要求,不会影响的最终答案,永远是你加的东西和加的东西比,字符串中原本存在的和存在的比!
- 经典解法的时间复杂度为O(n^2)
Manacher的时间复杂度为O(n)
- 和kmp算法有点像,是因为存在一个加速过程。类似于KMP的arr
需要知道的几个关键信息
- 回文直径和回文半径
- 也就是回文字符串的总长度
- 回文半径即回文字符串一半的长度
- 当前回文所扩展的最右边界R
- 当前回文所扩展的最右边界的对称轴C
- 当前回文所扩展的最右边界数组。
- C和R的关系:C刷新,R也随之刷新。
声明几个变量
- index:当前坐标
- index‘ :当前坐标通过C的对称点
根据index的坐标位置和index‘的回文半径确定了2种大情况
- 第一种大情况
- index的坐标大于R,暴力扩就是了,index向右推移,R的数值和C的数值随之更新
- 第二种情况
- index的坐标在R内,index根据C的对称点index‘ 的回文直径 与 R根据C的对称点L之间的关系又分为3种状况
- index‘的回文区域在L和R范围内
- 这种情况index的回文就等于index‘的回文,长度一样,其次,内容一样
- 证明
![](https://i-blog.csdnimg.cn/blog_migrate/176cd6158f71b0db285131aa81e6c460.png)
- index‘的回文区域与L点重合
- 这种情况下,index的回文半径最小为index到R的距离,但是,是否还有更大的回文半径,需要暴力判定
- 证明
![](https://i-blog.csdnimg.cn/blog_migrate/ef4d29c4703a2cddb6d783ebcd20f171.png)
- index‘的回文区域超过L点
- 可以判定不用验证的区域为index到R的范围,
- 证明
![](https://i-blog.csdnimg.cn/blog_migrate/1bbb4c4b0600cfc6fbd93803fe96d584.png)
package LiKou.Manacher;
public class Manacher {
public static void main(String[] args) {
String str = "abccbacfdakae";
String over = toPrefectStr(str);
int i = findPalindrome(over);
System.out.println(i);
}
private static String toPrefectStr(String str) {
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append("#");
for (int i = 0; i < str.length(); i++) {
stringBuffer.append(str.charAt(i));
stringBuffer.append("#");
}
return stringBuffer.toString();
}
private static int findPalindrome(String str) {
int arr[] = new int[str.length()];
int c = -1;
int R = -1;
int max = -1;
for (int i = 0; i < str.length(); i++) {
int left = i - 1;
int right = i + 1;
//暴力递归
if (i >= R) {
//刷新i值
R = i;
c = i;
int count = 0;
arr[i] = 1;
//往两边扩
while (left >= 0 && right <= str.length() - 1) {
//如果能扩动,扩
if (str.charAt(left) == str.charAt(right)) {
//左右重新设定值
left--;
right++;
//R刷新
R++;
count++;
arr[i] += 2;
} else {
break;
}
}
max = max < arr[i] ? arr[i] : max;
} else {
//这下就要分情况了(在范围内,超过范围,刚刚好)
//主要是找到i'
int i1 = c * 2 - i;
//然后找到i1的回文半径
//int radius = arr[i1];
//找到R根据C的对称点L
int L = c - (R - c);
//然后就要判断i1的回文半径和L的状态(在L的范围内,超出L,和L重合)
if ((i1 - arr[i1] / 2) > L) {
arr[i] = arr[i1];
} else if ((i1 - arr[i1] / 2) == L) {
//需要重新设定,并且看看R要不要刷新
//获取L'的对称点
int LL = i * 2 - R;
int sr = R + 1;
int sl = LL - 1;
arr[i] = (R - i) * 2 + 1;
while (sr <= arr.length - 1 && sl >= 0) {
if (str.charAt(sr) != str.charAt(sl)) {
break;
}
sl--;
sr++;
R++;
arr[i] += 2;
}
max = max < arr[i] ? arr[i] : max;
} else {
arr[i] = (R - i) * 2 + 1;
}
}
}
return max/2;
}
}