最近遇到了要求解最长回文串的问题,最初做题时暴力解题TLE不说,还查看了别人的题解后发现,自己还少考虑了一种情况,所以今天就自己小小的总结一下。
回文串分为长度为奇数和长度为偶数两种,奇数的可以从开始一个字符一个字符的向两边寻找,而偶数的这样就不行了,于是这里我们可以稍微处理一下解决奇偶问题。重新定义一个数组,将原字符数组中的每个字符之间加上一个相同的字符,这个字符可以自己想加啥就加啥,不用介意,最初看着其他有说不要加与题目中有涉及的字符有关,不过我试着加了也能过,而且在用的过程中没有什么影响,就随自己加了。
比如一个字符串 iloveyou ,处理后变成 i#l#o#v#y#o#u ,新数组中的字符串就是奇数了,就能够像之前那样处理了,不过这样处理还是稍微时间复杂度还是稍高,当然数据低的话直接这样操作完全没有问题,但是如果数据大的话,这明显是不行的。所以就会用到接下来的Manacher 算法。
这个算法与我们之前说的处理方法只是多了一个步骤,也就是增加时间使用效率。
我们从第一个字符开始遍历,我们会得到每个字符所对应的最长回文字符串长度,并且我们可以把这个字符叫做当前回文串的轴,左右延伸的长度就可以定义为回文串的半径
比如: aba 字符b就是这个回文串的轴,2就是这个回文串的半径。
有了上面的概念,接下来我们可以每通过一个字符找回文串并且更新已经找到回文串所能够到的最远的那个字符,并且记录到达最远字符的那个位置,这个最远是对比右边。
比如: cbcbcysdkj 第一个字符c因为不能组成回文串,最远的就是自己本身,记录本身位置1,第二个字符b能到达最远的就是第三个字符,记录位置2,而第三个字符c最远就可以到达第五个字符c,记录位置3,这个最远就是这样。
求得这个最远后,还需要分情况考虑。就拿上面的 cbcbcysdkj 来说,因为每一次的更新所能到达的最远位置,处理第四个字符b的时候,b的位置是小于5的,这里就要用到节省时间的方法了。找到与第四个字符b关于记录位置对称位置的那个字符,这里就是第二个字符b,第二个字符我们已经找过了,并且可以知道其对应的回文串长度。而且这两个位置都处于以第三个字符c为轴的回文串中,所以在以第三个字符c为轴的回文串的范围内在找第四个字符的回文串长度的时候,我们就可以直接比较第二个字符b回文串长度和最远位置与第四个字符b的距离,最后取小,表示这个半径内关于第四个字符b是不用判断的,因为和对称位置的是相同的。
再举一个例子: aacb'ca‘a'acb'caa'c 假设当前我们判断的是第二个b字符回文串长度,找到对称位置,关于中间用"标记的字符a回文串最右的那个字符位置也标记出来了,我们可以看到在在当前最长的回文串中,两个对称位置在最长回文串的范围内所构成的回文串是相同的,所以我们就可以利用这个特点来解决问题。
下面就是代码:
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
char str[110005], nstr[220005];
int len, maxpos, maxright, sympos, pos[220005], syml, r;
int min(int a, int b)
{
if (a > b)
{
return b;
}
return a;
}
void Manacher()
{
pos[0] = 0;
pos[1] = 1;
maxpos = 1;//表示找到该位置所构成的回文串最右边那个字符位置编号最大的那个位置
int i;
for (i = 2; i <= len; i++)
{
maxright = pos[maxpos] + maxpos;
//这个maxright表示已经找到回文串最右边的那个字符的位置。
sympos = 2 * maxpos - i;
//这个是当前判断位置关于maxpos对称点的位置,本来是maxpos-(i-maxpos),这是化简后的式子
r = 1;
if (i <= maxright)//这个就是当当前判断位置在maxright的左边,就要查看对称点构成回文串长度
//然后与现在可以到达最右那个字符到当前位置的距离比较,取小的那个,我们就可以跳过已经找过的字符了。
{
syml = min(maxright - i, pos[sympos]);
r += syml;
}
for (r; i - r >= 0 && i + r <= len; r++)//要保证找的字符在范围内
{
if (nstr[i - r] != nstr[i + r])
{
break;
}
}
r--;
if (r + i > maxright)//如果到达的最右字符位置编号更大,就要更新记录的位置。
{
maxpos = i;
}
pos[i] = r;
}
}
int main()
{
while (scanf("%s", str) != EOF)
{
int i, po;
len = strlen(str);
po = 0;
for (i = 0; i < len; i++)
{
nstr[po++] = '#';
nstr[po++] = str[i];
}
nstr[po++] = '#';
nstr[po + 1] = '\0';
len = po;
Manacher();
int maxl = 0;
for (i = 0; i <= len; i++)
{
maxl = max(maxl, pos[i]);
}
printf("%d\n", maxl);
}
return 0;
}
上面代码以杭电3068题为例,可以去看一看,会做那个题也就差不多会了。
还有这个上面min我编译有问题就重新写了一个,如果能编译可以直接用。还有'#'可以换成你喜欢的字符