公众号:CppCoding
Manacher’s Algorithm,中文名叫马拉车算法,是一位名叫Manacher的人在1975年提出的一种算法,解决的问题是求最长回文子串,神奇之处在于将算法的时间复杂度精进到了O(N),下面我们来详细介绍下这个算法的思路。
马拉车算法本质还是中心扩散,为了避免回文串是奇数还是偶数的情况,要对原始的回文串字符进行添加分隔符操作。
第一步:添加分隔符
假设分隔符为“#”,在字符串“ababa”中添加后的结果是“#a#b#a#b#a#”。即添加后是结果字符串的总数是奇数的。
第二步:计算辅助数组
辅助数组的作用就是记录以每个字符串为中心的回文串的信息。
辅助数组中的值,就是以当前的字符为中心,同时向两边进行扩散一个位置,若两边相同,则继续扩散,一直等到不相同为止,在当前字符的值中记录下向两边扩散的位置大小。
例如图中3下标的字符,以 char[3] = ‘b’ 为中心,同时向左边向右扩散,走 1 步,左右两边都是 “#”,构成回文子串,继续同时向左边向右扩散,左边是 “a”,右边是 “b”,不匹配,最多能扩散的步数为 1 ,因此 p[3] = 1;
这种情况的辅助数组会导致一种计算错误的结果。
马拉车算法详解
那么下面我们就来看如何求p数组,需要新增两个辅助变量 mx 和 id,其中 id 为能延伸到最右端的位置的那个回文子串的中心点位置,mx 是回文串能延伸到的最右端的位置,需要注意的是,这个 mx 位置的字符不属于回文串,所以才能用 mx-i 来更新 p[i] 的长度而不用加1,由 mx 的更新方式 mx = i + p[i] 也能看出来 mx 是不在回文串范围内的,这个算法的最核心的一行如下:
p[i] = mx > i ? min(p[2 * id - i], mx - i) : 1;
#include <vector>
#include <iostream>
#include <string>
using namespace std;
string Manacher(string s) {
// Insert '#'
string t = "$#";
for (int i = 0; i < s.size(); ++i) {
t += s[i];
t += "#";
}
// Process t
vector<int> p(t.size(), 0);
int mx = 0, id = 0, resLen = 0, resCenter = 0;
for (int i = 1; i < t.size(); ++i) {
p[i] = mx > i ? min(p[2 * id - i], mx - i) : 1;
while (t[i + p[i]] == t[i - p[i]]) ++p[i];
if (mx < i + p[i]) {
mx = i + p[i];
id = i;
}
if (resLen < p[i]) {
resLen = p[i];
resCenter = i;
}
}
return s.substr((resCenter - resLen) / 2, resLen - 1);
}
int main() {
string s1 = "12212";
cout << Manacher(s1) << endl;
string s2 = "122122";
cout << Manacher(s2) << endl;
string s = "waabwswfd";
cout << Manacher(s) << endl;
}