背景介绍:
给出一个字符串,求最长的回文字串,如果暴力的话,就是遍历所有点,以每个点当作中心向两边拓展寻找。这样的话,时间复杂度为O(n2);如果字符串长度很大的话,暴力的时间就有点长。这个时候Manacher 算法应运而生,也就是Manacher’s Algorith,俗称为马拉车算法。它的时间复杂度为O(n)。虽然作用比较单一,但还是掌握的为好。
算法过程:
一,插入#字符
为了能够将字符串统一起来,也就是让字符串的个数全都为奇数情况,马拉车算法提供了一种巧妙的方法:在每两个字符之间插入分隔符(包括首尾两端),分隔符一定为字符串中没有出现过的,比如#等字符。这样的话,如果字符串为偶数,则需要添加奇数个字符,二者相加为奇数,如果字符串为奇数,需要添加偶数个字符,相加还是为奇数。
eg:
abc
#a#b#c#
二,插入 $ 字符
为了能够让算法不会有越界情况发生,还要再字符串首尾加上另一种相当于用来结束的字符如$等,这样就保证了匹配到头或者尾的时候一定是不相等,就不会有越界了。当然对于尾部来说也可以不添加 $ 字符,因为字符串的结尾是通过’\0‘来实现的。上面保证了字符串数量一定为奇数,在这里加上两个字符后还是为奇数。
eg:
abc
$#a#b#c#
三,数组p的计算:
马拉车算法之所以能够在O(n)时间内完成,就是因为它巧妙地应用了回文串的特性,什么特性呢?举个栗子啊,比如aaaaaaa,如果暴力来做的话,会发生多个回文串重叠问题,也就是会重复计算多次,马拉车算法通过一个p数组来避免这种情况发生。
那么p数组这么的好,它里面存的是什么呢?其实p数组内部存的就是当前位最长的回文字串的半径,对于最长的回文字串长度,只要遍历p数组找到最大值,然后减一就是了。减一的原因是去掉多出来的’#‘字符。
eg:
接下来进入正题,如何求p数组呢?
从左向右的计算p数组,有一个mi为当前最长的字符串的中心。R为mi的半径,也就是最长能到达右端的长度。
计算p数组会有两种情况:
(1)i<=R情况,如何计算p[i]的值?因为我们的i是关于mi对称的一个点并且其与mi的距离不超过mi的半径R,所以一定能找到一个与i对称的点j在mi的左面,也就是2*mi-i的位置。那么接下来又可以分为两种情况。
a),p[j] < R-i。什么意思呢?这也就是说明了以点j为中心的回文串没有超出当前最长的回文串mi,那么根据字符串的对称性,我们就能得出p[i]=p[j],如图所示:
b), p[j] >= R-i。这也就是说明了以点j为中心的回文串超出了当前最长的回文串mi,这个时候对于i回文串和j回文串来说,并不一定能够总使得p[i] = p[j]。但根据回文串性质,一定可以知道至少p[i] = R-i的,至于能不能继续扩大,就得根据后续操作来判断了。如图所示:
(2)当i>R的时候。这个时候是没有办法根据回文串性质去简化的,只能一步一步的去匹配。注意在计算的过程中,要不断的更新mi的值。
代码:
int Manacher(string s)
{
string res = "$#";
for (int i=0; i<s.size(); i++)
{
res+=s[i];
res+="#";
}
vector<int> p(res.size(), 0);
int mi = 0, right = 0;
int maxLen = 0, maxPoint = 0;
for (int i=1; i<res.size(); i++)
{
p[i] = right>i ? min(p[2*mi-i], right-i) : 1; //核心内容
while (res[i+p[i]] == res[i-p[i]])
{
p[i]++;
}
//更新right和mi的值
if (right < i+p[i])
{
right = i+p[i];
mi = i;
}
if (maxLen < p[i])
{
maxLen = p[i];
maxPoint = i;
}
}
return maxLen-1;
}
推荐例题:
poj3974:
地址:http://poj.org/problem?id=3974
一道模板题,没有啥好讲的。
AC代码:
#include <iostream>
#include <string>
#include <vector>
using namespace std;
int Manacher(string s)
{
string res = "$#";
for (int i=0; i<s.size(); i++)
{
res+=s[i];
res+="#";
}
vector<int> p(res.size(), 0);
int mi = 0, right = 0;
int maxLen = 0, maxPoint = 0;
for (int i=1; i<res.size(); i++)
{
p[i] = right>i ? min(p[2*mi-i], right-i) : 1;
while (res[i+p[i]] == res[i-p[i]])
{
p[i]++;
}
if (right < i+p[i])
{
right = i+p[i];
mi = i;
}
if (maxLen < p[i])
{
maxLen = p[i];
maxPoint = i;
}
}
return maxLen-1;
}
int main()
{
ios::sync_with_stdio(false);
int t = 1;
string s;
while (cin >> s && s!="END")
{
cout << "Case " << t++ << ": " << Manacher(s) << endl;
}
return 0;
}