求最长回文串可以使用manacher算法来达到O(n)时间内得出结果,之所以降到O(n)是因为减少了很多重复匹配。
思路如下:
1.把所有字符串都变成奇数个字母的串,方法很简单,就是在所有字母前后加一个特殊字符,比如常用’#’,这样长度为n的串就变成了长度为2*n+1的串,即奇数串,然后在为了在代码中减少边界的判断,分别在首位加另外的特殊字符,一般在首部加个’$’,而尾部自然用字符串的结尾标志’\0’即可,所以总长度为2 *n + 3,另外最后一个字符在新串中的下标为2 *n,所以p数组只需要申请2 *n+1个元素即可。
如:abcd 变成$#a#b#c#d#,最后的’\0’不显示。
2.减少重复查找的关键地方就是如下代码中的
if (i < mx)
{
p[i] = minx(p[2 * id - i], mx - i);
}
这里分析一下:
当 mx - i > P[j] 的时候,以S[j]为中心的回文子串包含在以S[id]为中心的回文子串中,由于 i 和 j 对称,以S[i]为中心的回文子串必然包含在以S[id]为中心的回文子串中,所以必有 P[i] = P[j],见下图。
当 P[j] >= mx - i 的时候,以S[j]为中心的回文子串不一定完全包含于以S[id]为中心的回文子串中,但是基于对称性可知,下图中两个绿框所包围的部分是相同的,也就是说以S[i]为中心的回文子串,其向右至少会扩张到mx的位置,也就是说 P[i] >= mx - i。至于mx之后的部分是否对称,就只能逐个去匹配了。
对于 mx <= i 的情况,无法对 P[i]做更多的假设,只能P[i] = 1,然后再去匹配了。
3.另外,转换后的字符串的回文子串的半径(即P[]的值)减1就是原字符串中回文子串的长度(包含的任何一个回文子串都有这个关系)
代码如下:
int maxPalin(const char *str)
{
unsigned len = strlen(str);
char *s = new char[2 * len + 3];
int *p = new int[2 * len + 1];
s[2 * len + 2] = '\0';
s[0] = '$';
for (unsigned i = 0; i < len; ++i)
{
s[2 * i + 1] = '#';
s[2 * i + 2] = str[i];
}
s[2 * len + 1] = '#';
int id = 0;
int mx = 0;
int res = -1;
for (unsigned i = 1; i <= 2 * len; ++i)
{
if (i < mx)
{
p[i] = minx(p[2 * id - i], mx - i);
}
else
{
p[i] = 1;
}
while (s[i + p[i]] == s[i - p[i]])
{
++p[i];
if (p[i] > res)res = p[i];
}
if (i + p[i] > mx)
{
mx = i + p[i];
id = i;
}
}
delete[] p;
delete[] s;
return res - 1;
}
博客地址:www.iaccepted.net