您的位置:首页 > 其它

Manacher算法 - 最长回文子串

2016-03-13 17:43 351 查看
如题,求字符串S的最长回文子串,暴力就不说了。

Manacher复杂度:O(n)。

首先,求回文的话奇数长度字符串和偶数长度字符串处理方式不同,为解决该问题,我们在字符串S每个字符后加上分隔符'#',同时为了减少边界判断,在字符串的最前面加个'$#',当然字符串本身不能出现过'$'和'#'。

abcbaaaabbc
$#a#b#c#b#a#a#a#a#b#b#c#


变量说明:

id: 目前已知的最长回文的中心点

mx:以id为中心的最长回文能到达的最远距离

p[i]:以s[i]为中心的最长回文往一侧能延伸的距离(包含s[i]自身)

以上回文的判断均含 分割符

下标   0 1 2 3 4 5 6 7 8 9...
串     $ # a # b # c # b # a # a # a # a # b # b # c #
p[i]   0 1 2 1 2 1 6 1 2 1 2 3 4 7 4 3 2 1 2 3 2 1 2 1


如上,p[4]为2是"b#",然后有结论原串最长回文长度 = max{ p[i] } - 1,证明略。那么问题即为如何求p[i]。

算法思想:

根据已知信息确定p[i]的最小值,即p[i] >= ?,就不用从1开始再算了,从而省时间。

假设现id为已知最长回文的中心点,i是当前下标,j为i以id为中心的对称点,j 的回文情况已知(根据对称得下标为2 * id - i )。

分两种大情况。

情况一:mx > i

子情况一:mx - i >= p[ j ],即 i到mx距离 >= Lj到Cj距离



图中紫色部分为i ,j各自的回文范围,则因为[ Lj...Cj ]和[ Cj...Rj ]回文,[ j...id ] 和[ id...i ] 回文,所以 [ Lj...Cj ]与[ Li...Ri ]回文,即p[ i ] >= p[ j ],代码先直接赋值为p[ j ],具体的值之后在暴力找就行了。

子情况二:mx - i < p[ j ],即 i到mx距离 < Lj到Cj距离



此时Lj在mx'的左边,而mx'左边和mx右边已经没有回文特征了,无法得出p[ i ] >= p[ j ]的结论,但p[ i ] 肯定超出mx,因为mx范围内还是有以id为中心的回文特征的,所以p[ i ] >= mx - i。

情况二:mx < i,此时没有任何已知信息可以用,只能p[ i ] = 1开始。



更新id的条件是找到更远的mx,因为mx更远可以获取更多信息,当然设置为p[i]更大为条件答案也对,不过会慢很多。

以上内容表示为代码:

if (mx > i) {
if (mx - i >= p[j]) {
p[i] = p[j];
} else {
p[i] = mx - i;
}
} else {
p[i] = 1;
}


也即

p[i] = ( mx-i >= p[j] ) ? p[j] : mx - i;


至此就可以写出完整代码了。

#include <cstdio>
#include <cstring>
#include <string>
#include <iostream>
#include <algorithm>
using namespace std;
#define ll long long
#define RE freopen("1.in","r",stdin);
#define WE freopen("1.out","w",stdout);
#define bug(x) cout<<#x<<":"<<(x)<<endl;

const int maxn = 110000;

int p[maxn * 2 + 5];    //记得*2

int Manacher(string s) {
int ans = 2;  //ans = max{p[i]},因为要-1,所以赋为2,回文最少1长度
int id = 1, mx = 1;             //0号是$,没必要算
memset(p,0,sizeof(p));

for (int i = 1; i < s.length(); ++i) {
int j = 2 * id - i;         //对称点
if (mx > i) {               //i在已探知范围内
if (mx - i >= p[j]) {   //右边比较长,则对称性得知p[i]>=p[j]
p[i] = p[j];
} else {                //左边比较长,即j的回文超出了mx对称点,则i的回文至少到mx
p[i] = mx - i;
}
} else {                    //未探知的就老老实实来
p[i] = 1;
}
//上面说明了p[i]>=某个值,具体多少得暴力继续
while (s[ i + p[i] ] == s[ i - p[i] ] ) { //有i+p[i]溢出,但不会有负下标溢出,不慌
p[i]++;
}
if (i + p[i] > mx) {    //更新最右点和对应的id
id = i;
mx = id + p[id];
}
ans = max(ans, p[i]);
}
return ans - 1;
}

int main() {
// RE
std::ios::sync_with_stdio(false);
string str, s1;
while (cin >> str) {
s1 = "$#";
for (int i = 0; i < str.length(); ++i) {
s1 += str[i];
s1 += "#";
}
// cout << s1 << endl;
cout << Manacher(s1) << endl;
}
return 0;
}


无注释版

#include <cstdio>
#include <cstring>
#include <string>
#include <iostream>
#include <algorithm>
using namespace std;
#define RE freopen("1.in","r",stdin);
#define WE freopen("1.out","w",stdout);
#define bug(x) cout<<#x<<":"<<(x)<<endl;

const int maxn = 110000;

int p[maxn * 2 + 5];

int Manacher(string s) {
int ans = 2;
int id = 1, mx = 1;
memset(p,0,sizeof(p));

for (int i = 1; i < s.length(); ++i) {
int j = 2 * id - i;
if (mx > i) {
p[i] = ( mx-i >= p[j] ) ? p[j] : mx - i;
} else {
p[i] = 1;
}
while (s[ i + p[i] ] == s[ i - p[i] ] ) {
p[i]++;
}
if (i + p[i] > mx) {
id = i;
mx = id + p[id];
}
ans = max(ans, p[i]);
}
return ans - 1;
}

int main() {
// std::ios::sync_with_stdio(false);
string str, s1;
while (cin >> str) {
s1 = "$#";
for (int i = 0; i < str.length(); ++i) {
s1 += str[i];
s1 += "#";
}
cout << Manacher(s1) << endl;
}
return 0;
}


练习地址:http://acm.hdu.edu.cn/showproblem.php?pid=3068

英文教程链接(里面图不错,虽然内容好像有点错的):http://articles.leetcode.com/longest-palindromic-substring-part-ii
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: