算法介绍
给定字符串str,若s同时满足以下条件:
s是str的子串
s是回文串
则,s是str的回文子串。
算法的要求,是求str中最长的那个回文子串。
算法详解
第一种:枚举中心方法
循环索引,判断以某索引位开始判断前一半和后一半是否相等,如果相等计算总长度。
int enumCenter(char *S) {
int i, j, max;
int start = 0;
int sSize = strlen(S);
if (S == 0 || sSize < 1) {
return 0;
}
max = 0;
for (int i = 0; i < sSize; i++) {
// 如果以i为中心是奇数的回文子串
for (int j = 0; (i - j >= 0) && (i + j < sSize); j++) {
if (S[i - j] != S[i + j]) {
break;
}
if (j * 2 + 1 > max) {
start = i - j;
max = j * 2 + 1;
}
}
// 如果以i为中心是偶数的回文子串
for (int j = 0; (i - j >= 0) && (i + j + 1 < sSize); j++) {
if (S[i - j] != S[i + j + 1]) {
break;
}
if (j * 2 + 2 > max) {
start = i - j;
max = j * 2 + 2;
}
}
}
for (int k = start; k < start + max; k++) {
printf("%c",S[k]);
}
printf("\n");
return max;
}
第二种:Manacher算法
因为回文串有奇数和偶数的不同。判断一个串是否是回文串,往往要分开编写,造成代码的拖沓。
因此我们需要想一个办法能否将字符串改造成都是奇数或者偶数呢。
一个简单的事实:长度为n的字符串,共有n-1个“邻接”,加上首字符的前面,和末字符的后面,共n+1的“空”(gap)。因此,字符串本身和gap一起,共有2n+1个,必定是奇数;
abbc → #a#b#b#c#
aba → #a#b#a#
由于在应用中心枚举的方式,会一位一位索引确定中心位置,那我们能否应用求取的信息获得后面的信息呢。
manacher算法就是应用前面各位置求取的回文子串长度位置信息来推断未求取中心的回文子串长度。
回文子串对应形式
首先假定我们已经将中心位置索引到了第i位置
当枚举到第个字符的时候,会出现两种情况,第一种就是i在某个最长回文子串中,第二种就是i不在某个最长回文子串中
第一种:i在某个最长回文子串中
该中情况下同样存在两种情况
1)i在回文子串中,并且j的回文半径没有超过ct回文半径
i为所求回文子串中心,j点为通过中心对称的点,p[i]为i点回文子串半径,p[j]为j点回文子串半径
可以看到,i与j通过ct对称,由于ct两侧的字符相同,并且p[j]还在ct半径之内,那么可以通过j点的回文长度推断出i点的回文长度
2)i在回文子串中,并且j的回文半径超过ct回文半径
此时求取i的半径,求取的是p[i]和R-i中较小的值。min(p[ct*2-i],R-i),可以推测出ct*2-i就是j的坐标位置,比较的就是图中紫色部分较小的长度。
通过上面2中情况可以看出,当p[j]没有超过L范围,那么min(p[ct*2-i],R-i)取得就是p[j]的值,此时p[j]=p[i],如果超过取得就是R-i取得也是p[i]能取得值,通过这个式子将p[i]赋值,然后依次向外推寻找是否还有满足回文子串条件,对应第一种外推一次就不满足条件,第二种就要看R外面是否还有满足条件的地方了。
第二种:i不在某个最长回文子串中
那么由于我们没有给定的条件支持该位置的信息,我们只能通过该点暴力左右扫描应用枚举的方式确定回文子串长度了。
代码如下:
代码重新组合:
// 将字符串abcba拼成$#a#b#c#b#a形式
void stringChange(char *S, char *CG) {
int sSize = strlen(S);
CG[0] = '$';
for (int i = 1; i < 2*sSize; i++) {
if (i % 2 == 0) {
CG[i] = S[i / 2];
}
else {
CG[i] = '#';
}
}
}
manacher算法:
int enumCenter(char *S) {
int i, j, max;
int start = 0;
int sSize = strlen(S);
if (S == 0 || sSize < 1) {
return 0;
}
max = 0;
for (int i = 0; i < sSize; i++) {
for (int j = 0; (i - j >= 0) && (i + j < sSize); j++) {
if (S[i - j] != S[i + j]) {
break;
}
if (j * 2 + 1 > max) {
start = i - j;
max = j * 2 + 1;
}
}
for (int j = 0; (i - j >= 0) && (i + j + 1 < sSize); j++) {
if (S[i - j] != S[i + j + 1]) {
break;
}
if (j * 2 + 2 > max) {
start = i - j;
max = j * 2 + 2;
}
}
}
for (int k = start; k < start + max; k++) {
printf("%c",S[k]);
}
printf("\n");
return max;
}
参考资料: