算法简介
马拉车算法是用来找字符串中的最大回文子串的算法
初始化字符串
回文可能面对奇偶回文串的问题,可以将字符串特殊处理全部变成偶数或奇数,我这是直接在首尾和每个字符前加上#
变成偶数
例如aabacba
转换成##a#a#b#a#c#b#a#
中心拓展
由第一个字符出发,遍历一遍字符串,并且以这个字符为中心向两边拓展,找回文半径,并确定回文右边界
我们没有必要为每一个字符的回文半径从0开始拓展,每一个字符的回文半径都可以根据对称性先来确定一个最初半径,然后拓展,这样可以节省很多时间
设回文中心为mid,mid的回文半径为R,最大右边界就是mid+R
当i < mid+R时i关于mid的对称点为j=2*mid-i
由对称可知假如j是一个回文中心,那么在以i为中心的字符串也可能存在回文,所以i的回文半径为min(以j为中心的回文半径,i到mid+R的距离),因为超出了mid的回文半径的话,就不能根据对称来确定了,所以先暂时确定i的回文半径
当 i>=mid+R时,就只能自己回文了,半径为1
每次i的回文半径超出了原来的最大回文右边界,就要更新mid和边界
确定回文串起点和长度
以每一个字符为中心的回文长度就是回文半径-1了,因为有#
符号在相当于把字符长度翻倍了,同理也不难推出回文起点
#include <iostream>
#include <string>
#include <cmath>
#include <cstring>
using namespace std;
string init(string in)
{
string str("##");
for (string::iterator it = in.begin(); it != in.end(); it++)
{
str.append(1, *it);
str.append("#");
}
return str;
}
int *manacher(string &str)
{
int *radius;
int mr = 0; //maxright 为回文字符串的右边界
int mid=0; //回文的中点
int n = str.length();
radius = new int[n + 1]();
for (int i = 1; i < n; i++)
{
if (i < mr)
{
// i 一定大于mid
//当i小于右边界时根据对称求得一部分回文半径,后面在for循环里继续向两边拓展可以较快地求得回文半径
//这里min很灵活,因为我们不知道右边界右边的情况,所以我们取对称点的回文半径和i到右边界之间的距离的较小的那一个
radius[i] = min(radius[(mid << 1) - i], radius[mid] + mid - i);
}
else
{
//当i超出了回文的右边界,说明前面不构成回文条件,只有自己与自己回文
radius[i] = 1;
}
for (; str[i + radius[i]] == str[i - radius[i]]; radius[i]++)
;//向两边拓展,半径增加
if (radius[i] + i > mr)
{
//更新边界和中心
mr = radius[i] + i;
mid = i;
}
}
return radius;
}
int main()
{
string str;
cin >> str;
str = init(str);
// cout << str << endl;
// return 0;
int *radius = manacher(str);
int ans = 1;
int index = 0;
int n = str.length();
for (int i = 0; i < n; i++)
{
if (radius[i] > ans)
{
ans = radius[i];
index = i;
}
}
if (ans == 2)
{
cout << 0 << endl;
}
else
{
cout << ans - 1 << endl;
for (int i = index - ans + 2; i < index + ans; i += 2)
{
cout << str[i];
}
return 0;
}
}
结果
aAabccbaABcdcBA
8
AabccbaA