manacher算法
参考博客:
1.https://www.cnblogs.com/cloudplankroader/p/10988844.html
2.https://blog.csdn.net/xingyeyongheng/article/details/9310555
1.首先讲最普通的遍历,从0 ~ len,以每一个字符为中心,向左右开始遍历,判断这两个字符是否相等,这种遍历有个缺点就是对于偶数长度字符串无法处理,从上面的描述中可以看出找到的回文子串的长度也一定是奇数。时间复杂度是O(n^2)
2.而manacher算法是先对原字符串进行预处理在开头和结尾处,以及任意两个相邻字符之间都加上一个相同的且没在原串中出现的字符,这么做不会对该串是否回文造成影响举个例子对于“abab”,变为“#a#b#a#b#”原本最长回文子串是“aba”(或“bab”),现在是“#a#b#a#”,”aba“仍是回文子串的一部分,只是长度发生了变化,而这个两个串的长度又是有一定关系的。
3.我们需要维护什么?
(1).par[i]:代表以i为中心的最长回文子串的半径
(2).最右回文边界r:对于每个字符都会得到一个以它为中心的最长回文子串,r是所有已知的回文子串中最靠右的一个,也就是说在遍历的过程中r只可能增大不可能变小
(3).回文中心c:取得当前r的第一次更新时的回文中心
4.大体过程
(1)预处理原字符串
(2).初始化变量,r和c都初始化-1,r实际是最右边界位置的右一位
(3).开始遍历
如果i > r,就是普通的暴力匹配,记得看是否需要更新r和c
如果i < r,就是说当前这个点在某个回文子串中
之后找到i关于c对称的点i’,因为这两个点在一个回文子长字串中,所以以i为中心的回文子串与以i’为中心的回文子串有一定的关系,接下来我们需要判断以i’为中心的回文子串的特征
有三种可能:
a.i’的回文区域在c的回文区域内,那i的回文区域于i‘的回文区域相等
b.i’的回文区域左界长超过了c的回文区域,此时i的会问半径是i到r
c.i’的回文区域左界正好等于c的回文区域左界,那么i的回文半径至少是i到r,并且还可能继续向外匹配。
总结上面就是确定初识par[i]的值,这个值与i和r的关系有关i > r,则直接等于1,否则初始值等于i到r的距离和i’的回文半径中的最小值,由此再判断可不可以继续向外匹配。
时间复杂度是O(n)的
5.核心代码
for(int i = 0; i < len; ++i)
{
//确定初识par[i]的值
if(i < r) par[i] = min(r - i, p[2 * c - i]);
else par[i] = 1;
//继续向外匹配,但要注意别超过了定义域
while(i + par[i] < len && i - par[i] > -1)
{
if(ch[i + par[i]] == ch[i - par[i]]) ++par[i];
else break;
}
//看看最右界是否发生变化了
if(i + par[i] > r)
{
r = i + par[i];
c = i;
}
//更新最大值
mx = max(mx, par[i]);
}
AC代码:
#include <bits/stdc++.h>
using namespace std;
char s[110050];
int index, mx, len;
int r, c;
int par[220050];
char ch[220050];
int main()
{
while(~scanf("%s", s))
{
if(strlen(s) == 0)
{
printf("0\n\n");
continue;
}
len = strlen(s) * 2 + 1;
index = 0;
for(int i = 0; i < len; ++i)
{
if(i & 1) ch[i] = s[index++];
else ch[i] = '#';
}
r = -1;
c = -1;
mx = 0;
for(int i = 0; i < len; ++i)
{
if(r > i) par[i] = min(par[2 * c - i], r - i);
else par[i] = 1;
while(i + par[i] < len && i - par[i] > -1)
{
if(ch[i + par[i]] == ch[i - par[i]]) ++par[i];
else break;
}
if(i + par[i] > r)
{
r = i + par[i];
c = i;
}
mx = max(mx, par[i]);
}
printf("%d\n", mx - 1);
}
return 0;
}