看了很多文章,想了很久终于搞明白了,在这写一篇笔记供后来人学习和自己复习
个人认为讲的不错的文章和视频:
比较易懂的 Manacher(马拉车)算法配图详解 - 知乎 (zhihu.com)
马拉车算法 | Coding Club_哔哩哔哩_bilibili
题目:5. 最长回文子串 - 力扣(LeetCode)
manacher算法:又名马拉车算法,能在o(n)的时间复杂度内找到最长回文子串
模板:
class Solution {
public:
//p[i]记录回文子串的半径(包含作为对称轴的那位),如:
//i 0 1 2 3 4
//newstring # a # a #
//p[i] 1 2 3 2 1 其中p[3]的半径为#a#,为3
//2010的大小根据实际要求的字符串大小改变
int p[2010] = { 0 };
//预处理字符串,如:将字符串aa处理为#a#a#
string newString(string& s) {
string newstring = "";
for (int i = 0; i < s.size(); i++) {
newstring += "#";
newstring += s[i];
}
return newstring + "#";
}
int Manacher(string& s) {
//若为空字符串返回0
if (s.size() == 0)return 0;
//预处理字符串
string newstring = newString(s);
//right为最右回文边界,center为right对应的最左回文中心
//maxLen为字符串s的最长回文子串的长度(不是newstring,是s!!!)
int right = -1, center = 0, maxLen = -1;
for (int i = 0; i < newstring.size(); i++) {
//取得可能的最短的回文半径
//i>right时,最短回文半径是1
//i<right时,最短回文半径可能是p[center * 2 - i](i对center作对称取得的i'的回文半径)
// 或i到right的距离
if (i < right)p[i] = min(p[center * 2 - i], right - i);
else p[i] = 1;
//从边界暴力搜索最长回文子串,由于回文串的对称,此处while进行的遍历不会太多
while (i + p[i]<newstring.size() && i - p[i]>-1) {//防止数组越界
//假设newstring为 #a#
//i 0 1 2
//newstring # a #
//p[i] 1 2 1
//
//p[1]=2,当a自身是回文串的时候加上a两边的"#"也会是回文串,
//a换成"b#b""a#c#a"等任何一个回文串都是一样的规律,
//所以p[i]的大小会比实际回文串的大小多1
if (newstring[i - p[i]] == newstring[i + p[i]])p[i]++;
else break;
}
//更新center和right
if (right < p[i] + i) {
center = i;
right = p[i] + i;
}
//因为p[i]的大小会比实际回文串的大小多1,所以这里要-1
maxLen = max(maxLen, p[i] - 1);
}
for (int i = 0; i < 5; i++)cout << p[i] << " ";
cout << endl;
return maxLen;
}
};
在这个模板上可做许多回文串的题目,如:5. 最长回文子串 - 力扣(LeetCode)
在模板基础上加一个变量记录最长回文子串的起始下标即可
class Solution {
private:
int p[2010] = { 0 };
//cen记录最长回文子串的起始下标
int cen = 0;
string newString(string& s) {
string newstring = "";
for (int i = 0; i < s.size(); i++) {
newstring += "#";
newstring += s[i];
}
return newstring + "#";
}
int Manacher(string& s) {
string newstring = newString(s);
int center = 0, right = -1, maxLen = -1;
for (int i = 0; i < newstring.size(); i++) {
if (i < right)p[i] = min(p[center * 2 - i], right - i);
else p[i] = 1;
while (i + p[i]<newstring.size() && i - p[i]>-1) {
if (newstring[i - p[i]] == newstring[i + p[i]])p[i]++;
else break;
}
if (right < p[i] + i) {
center = i;
right = p[i] + i;
}
if (p[i] - 1 > maxLen) {
maxLen = p[i] - 1;
//根据回文串的对称性计算得出起始下标
cen = (center - p[i] + 1) / 2;
}
}
return maxLen;
}
public:
string longestPalindrome(string s) {
int temp = Manacher(s);
return s.substr(cen, temp);
}
};