Manacher
Manacher算法要解决的问题是在一个字符串中寻找最大回文子字符串长度。
该问题的暴力解法是遍历每一个字符,从该字符向两边扩展,寻找边界。
但该方法中,每次扩展得到的信息并不能被充分利用,设一个字符串如下:
标记为灰色的回文子串中,右侧的’a’‘b’'a’三个字符为中心的最大回文子串长度与左侧的对称点的相应信息是完全相同的,Manacher就是利用这个信息对暴力解法进行了加速。
辅助字符串
回文字符串可能是奇数或偶数个,为了在算法中不考虑奇数和偶数的情况,对原字符串进行扩充:
在所有字符中间插入一个辅助字符‘#’
(可以是任意字符,包括原数组中存在的,不影响结果),统计每个字符位的最大回文半径
(右边界减中心点+1),则原字符串中每个字符的最大回文长度
即为在辅助字符串中对应字符的最大回文半径-1
。
Manacher加速过程
遍历辅助字符串,维护以下几个变量:
int i
:当前到达字符的索引;
int R
:到目前为止右侧最远的回文右边界(在上图中,当遍历到’d’时,R直接到达到灰色部分的右边界);
int c
:到目前为止最远回文右边界对应的中心字符;
int radius[aux.size()]
:整数数组,记录每个字符的最大回文半径。
int maxVal
:记录到目前为止最大的radius[i],准备作返回值。
Manacher思想如下:
若i>R,则从i为止暴力扩充得到radius[i]和新的R;
若i<R,则i在之前扩充到的回文区域内,这时就可以看i相对c的对称点2 * c - i
, 若radius[2*c - i]小于或者大于R - i, 则radius[i]就等于radius[2*c - i];若radius[2 * c - i]恰好等于R - i(压线),则需要从边界处继续暴力扩充,得到新的radius[i]和新的R。
代码如下:
int check(string& s){
string aux(2 * s.size() + 1, '0');
int index = 0;
for(char& c : aux){
c = (index % 2 == 0) ? '#' : s[index / 2];
index++;
}
vector<int> radius(aux.size());
int R = -1;
int i = 0;
int C = -1;
int maxVal = 0;
while(i < radius.size()){
radius[i] = i >= R ? 1 : min(R - i, radius[2 * C - i]);//记录当前已经得到的radius[i]部分
while(i + radius[i] < radius.size() && i - radius[i] >= 0){//合并了三种情况
if(aux[i + radius[i]] != aux[i - radius[i]])
{
maxVal = max(radius[i], maxVal);
break;
}
else radius[i]++;
if(i + radius[i] > R){
R = i + radius[i];
C = i;
}
maxVal = max(radius[i], maxVal);
}
i++;
}
return maxVal - 1;//辅助字符串的最大回文半径-1即为原字符串的最大回文长度
}