回文是什么
一个字符串正过来念和反过来念一样,总的来说就是有一个对称轴可能在字符上也可能在范围上面
回文暴力求法
首先以a为中心向两边扩散找回文,发现只有1是a的本身
以1位置为中心点扩大:
能扩大的长度为3,以2位置为中心向两边扩散回文最长为1,这样依次来扩,最终得到的回文序列为:
但是如上方法是搞不定偶数回文的情况的
在写代码的过程中可以处理一下:
有如下字符串:
那么我们可以处理为:
开头加一个#,以及每个字符的间隔都加一个#,然后以每一个为中心往左右两边扩,将得到的结果除以2。
将原始串转换为处理串,如果不用#,用一个在原始串中出现的字符,会不会干扰最终的处理结果?
不会的,因为往左右两边扩的时候是实际的字符和实际的字符相比,处理串新增的字符和处理串新增的字符比。
如上必然是个O(nxn的解)
如果使用Manacher算法则算法时间复杂度为O(N)
Manacher算法
回文直径和回文半径
回文直径是5,回文半径是3。
如1221,回文直径是4,回文半径是2
最右回文边界
用R表示,最开始R=-1
如果扩展的右边界在往右变大了,则更新R值
如上图R更新为0。
再到1位置往两边扩:
R来到2位置。
再从2位置往两边扩,来到4位置
R来到4位置。
再从3位置往两边扩,没有刷新右边界。
再从4位置往两边扩,没有刷新右边界
再从5位置向左右两边扩展,R来到10位置。
R始终记录着求当前遍历到回文串最右的位置
最右回文右边界的中心C位置
R只有更新,C就负责记录是以哪个位置为中心扩出来的R,C跟着R一起变
如上图从2位置扩展到4位置,R也来到4位置,则更新C为2位置。
Manacher求解过程
假设现在是在以i位置为中心向左右两边扩,
分为以下几种情况讨论:
第一种情况,i没有被R罩住(继续往左右两边扩)
第二种情况,i被R罩住 【存在优化:
C一定是i的左边的一个位置,我就能做出i关于C的对称点:
细分三种情况:
首先i在R外,暴力扩
根据i‘扩出来的回文区域分,i’是小于i的,所以当初是求过i‘扩出来的大小的,而且当初求的答案一定被保存在R里面的。
第一种情况:i’扩出来的区域彻底在L-R内
i是当前来到的位置,L-R是C位置字符扩出来的回文串。
如下例子:
现在来到i位置,
这种情况扩的范围是跟i‘一样的,因为L-R已经是一个回文了,假设i位置和i’位置扩出同样长的区域,
甲和乙是关于C对称的,所以甲和乙是逆序的关系,由于甲是回文,所以甲的逆序也就是乙是回文。
第二种情况:i‘的回文区域已经跑到L-R的外面去了
i’的回文区域跑到L-R的外面去了,不用扩了,i位置能扩多远回文半径就是i-R。
证明:
以i‘为中心左L的对称点L’,以i为中心做R的对称点R‘
L-L’(叫甲)关于C的对称就是R‘到R(叫乙)这一段,甲和乙在L-R这个大回文里面,是关于C对称的,甲和乙是逆序的,甲又在i’为中心的回文里面,甲一定是回文,那么乙一定是回文。
第三种情况:i‘的回文区域和L在一起的
一定知道i的回文区域至少和i’一样,但是会不会更大不知道。
】
整个时间复杂度为O(N),因为R不回退的
如下这段代码,分为两个条件i在R内以及i在R外,
i在R外扩充区域最小为1,就是i自己。
i在R内扩充区域分三种情况:
public class Manacher {
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位置扩出来的答案,i位置扩的区域,至少是多大。
//i在R内,i‘和i到R的距离谁小取谁
pArr[i] = R > i ? Math.min(pArr[2 * C - i], R - i) : 1;
//i加上一个不用验证的区域,i减去一个不用验证的区域不越界
while (i + pArr[i] < str.length && i - pArr[i] > -1) {
//再往右的字符和再往左的字符一样,则扩展回文半径
if (str[i + pArr[i]] == str[i - pArr[i]])
pArr[i]++;
else {
break;
}
}
//如果R更往右了,更新R更新C
if (i + pArr[i] > R) {
R = i + pArr[i];
C = i;
}
max = Math.max(max, pArr[i]);
}
//半径减去一
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;
}
// for test
public static int right(String s) {
if (s == null || s.length() == 0) {
return 0;
}
char[] str = manacherString(s);
int max = 0;
for (int i = 0; i < str.length; i++) {
int L = i - 1;
int R = i + 1;
while (L >= 0 && R < str.length && str[L] == str[R]) {
L--;
R++;
}
max = Math.max(max, R - L - 1);
}
return max / 2;
}
// for test
public static String getRandomString(int possibilities, int size) {
char[] ans = new char[(int) (Math.random() * size) + 1];
for (int i = 0; i < ans.length; i++) {
ans[i] = (char) ((int) (Math.random() * possibilities) + 'a');
}
return String.valueOf(ans);
}
public static void main(String[] args) {
int possibilities = 5;
int strSize = 20;
int testTimes = 5000000;
System.out.println("test begin");
for (int i = 0; i < testTimes; i++) {
String str = getRandomString(possibilities, strSize);
if (manacher(str) != right(str)) {
System.out.println("Oops!");
}
}
System.out.println("test finish");
}
}
2乘以C-i就是i‘,i’的回文半径长度和i-R的距离谁小,谁就是我至少不用验证的区域
Manacher 题
给定一个字符串,在字符串后面添加字符,至少添加几个字符让它整体都变成回文串。
【必须包含最后的字符的回文串最长是多长,然后将前面剩余的部分做逆序加到后面则解决问题】
求解方式:
当中心来到三角形位置的时候R包含最后一个字符,则停,已经找到了,将前面未包含的部分逆序到串后面则可以了。
package class28;
public class AddShortestEnd {
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;
}
}
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;
}
public static void main(String[] args) {
String str1 = "abcd123321";
System.out.println(shortestEnd(str1));
}
}