Manacher 算法模板
- 时间复杂度: O ( N ) O(N) O(N) ,由于 Manacher 算法只有在遇到还未匹配的位置时才进行匹配,已经匹配过的位置不再匹配,因此对于字符串 S 的每一个位置,都只进行一次匹配,算法的复杂度为 O ( N ) O(N) O(N) 。
- 空间复杂度: O ( N ) O(N) O(N) 。
五分钟学算法教程
老司机开车,教会女朋友什么是「马拉车算法」
code
class Manacher1 {
public:
static string longestPalindrome(string s) {
// 特判
int len = s.length();
if (len < 2) {
return s;
}
// 得到预处理字符串
string str = addBoundaries(s);
// 新字符串的长度
int sLen = 2 * len + 1;
// 数组 p 记录了扫描过的回文子串的信息
int p[sLen];
// 双指针,它们是一一对应的,须同时更新
int maxRight = 0;
int center = 0;
// 当前遍历的中心最大扩散步数,其值等于原始字符串的最长回文子串的长度
int maxLen = 1;
// 原始字符串的最长回文子串的起始位置,与 maxLen 必须同时更新
int start = 0;
for (int i = 0; i < sLen; i++) {
if (i < maxRight) {
int mirror = 2 * center - i;
// 这一行代码是 Manacher 算法的关键所在,要结合图形来理解
p[i] = min(maxRight - i, p[mirror]);
} else {
p[i] = 0;// 等于 R 的情况,注意这里的初始化
}
// 下一次尝试扩散的左右起点,能扩散的步数直接加到 p[i] 中
int left = i - (1 + p[i]);
int right = i + (1 + p[i]);
// left >= 0 && right < sLen 保证不越界
// str[left] == str[right] 表示可以扩散 1 次
while (left >= 0 && right < sLen && str[left] == str[right]) {
p[i]++;
left--;
right++;
}
// 根据 maxRight 的定义,它是遍历过的 i 的 i + p[i] 的最大者
// 如果 maxRight 的值越大,进入上面 i < maxRight 的判断的可能性就越大,这样就可以重复利用之前判断过的回文信息了
if (i + p[i] > maxRight) {
// maxRight 和 center 需要同时更新
maxRight = i + p[i];
center = i;
}
if (p[i] > maxLen) {
// 记录最长回文子串的长度和相应它在原始字符串中的起点
maxLen = p[i];
start = (i - maxLen) / 2;
}
}
cout << maxLen << endl;
return s.substr(start, start + maxLen);
}
static string addBoundaries(string s) {
int len = s.length();
if (len == 0) {
return "";
}
string builder;
for (int i = 0; i < len; i++) {
builder += '#';
builder += s[i];
}
builder += '#';
return builder;
}
};
知乎上的某篇教程,与上面的写法有点小区别,在开头结尾处添加了两个标志符号 ^ $
一文让你彻底明白马拉车算法
code
class Manacher2 {
public:
static string longestPalindrome(const string &s) {
string t = preprocess(s);
int n = t.length();
int P[n];
int C = 0, R = 0;
for (int i = 1; i < n - 1; i++) {
int i_mirror = 2 * C - i;
if (R > i) {
P[i] = min(R - i, P[i_mirror]);// 防止超出 R
} else {
P[i] = 0;// 等于 R 的情况
}
// 碰到之前讲的三种情况时候,需要利用中心扩展法
while (t[i + 1 + P[i]] == t[i - 1 - P[i]]) {
P[i]++;
}
// 判断是否需要更新 R
if (i + P[i] > R) {
C = i;
R = i + P[i];
}
}
// 找出 P 的最大值
int maxLen = 0;
int centerIndex = 0;
for (int i = 1; i < n - 1; i++) {
if (P[i] > maxLen) {
maxLen = P[i];
centerIndex = i;
}
}
cout << maxLen << endl;
int start = (centerIndex - maxLen) / 2; //最开始讲的求原字符串下标
return s.substr(start, start + maxLen);
}
static string preprocess(string s) {
int n = s.length();
if (n == 0) {
return "^$";
}
string ret = "^";
for (int i = 0; i < n; ++i) {
ret += '#';
ret += s[i];
}
ret += "#$";
return ret;
}
};