最长回文子串算法Manacher详解
完整代码如下:
class Solution {
public:
//manacher算法
void manacher(string& s, int n, vector<int>& mp){
string ms = "";
ms += "$#";
//预处理
for(int i = 0; i < n; i++){
//使之都变成奇数回文子串
ms += s[i];
ms += '#';
}
//目前已知的最长回文子串的最右一位的后一位
int maxpos = 0;
//当前的最长回文子串的中心点
int index = 0;
for(int i = 0; i < ms.length(); i++){
mp[i] = maxpos > i ? min(mp[2 * index - i], maxpos - i) : 1;
//两边扫
while(ms[i + mp[i]] == ms[i - mp[i]])
mp[i]++;
//更新位置
if(i + mp[i] > maxpos){
maxpos = i + mp[i];
index = i;
}
}
}
int getLongestPalindrome(string A) {
int n = A.length();
//记录回文子串长度
vector<int> mp(2 * n + 2);
manacher(A, n, mp);
int maxlen = 0;
//遍历数组
for(int i = 0; i < 2 * n + 2; i++)
//找到最大的长度
maxlen = max(maxlen, mp[i] - 1);
return maxlen;
}
};
算法概述
Manacher算法通过巧妙地利用回文串的对称性质,避免了重复计算,从而实现了线性时间复杂度。算法的核心思想是维护一个"当前已知的最右回文边界"和对应的中心点,利用对称性快速计算新位置的回文半径。
预处理阶段
string ms = "";
ms += "$#";
//预处理
for(int i = 0; i < n; i++){
//使之都变成奇数回文子串
ms += s[i];
ms += '#';
}
作用:
原始字符串中,回文子串可能是奇数长度或偶数长度。预处理将它们统一转换为奇数长度处理。
插入特殊字符(如’#')作为分隔符,例如"abc"变成"#a#b#c#"。
开头添加"$"是为了避免边界检查。
示例:
原始字符串 “aba” 预处理后变成 “$#a#b#a#”
主算法部分
int maxpos = 0; // 当前已知的最长回文子串的最右边界
int index = 0; // 对应的中心点
for(int i = 0; i < ms.length(); i++){
// 关键步骤:确定当前点的初始回文半径
mp[i] = maxpos > i ? min(mp[index - (i-index)], maxpos - i) : 1;
// 中心扩展
while(ms[i + mp[i]] == ms[i - mp[i]])
mp[i]++;
// 更新最右边界和中心点
if(i + mp[i] > maxpos){
maxpos = i + mp[i];
index = i;
}
}
关键变量:
mp[i]:记录以i为中心的最长回文子串的半径(包括中心点),每一轮循环结束mp[i]就确定好了
maxpos:当前已知的最右回文边界,说明此时已经有某个处理完的回文字串的右边界在这里
index:对应此时maxpos的回文中心
算法步骤:
1.初始半径确定:
mp[2 * index - i]是i关于index的对称点的半径,即为mp[index - (i-index)]
maxpos - i是i到maxpos的距离
取两者较小值作为初始半径(因为不能超过已知的回文范围)
如果i在maxpos的左侧,说明已经当前下标为i的ms元素已经在前面以index为中心半径为mp[index]的回文子串中。那么可以利用对称性快速获得初始半径:我们只需要找到它关于index对称的那一点的回文半径mp[index - (i-index)]就是这一点的回文半径了。但要注意,万一maxpos - i小于mp[index - (i-index)],说明和ms[i]关于index对称的那一点的回文半径比它到index边界距离还大,这样的话,这一点的部分回文元素就不在以index为中心,半径为mp[index]的范围内。这样我们就只能获取部分半径,但比1大已经很好了。
2.中心扩展:
从初始半径开始,向两边扩展,检查字符是否相同
每发现一对相同的字符,半径加1
3.更新边界:
如果当前回文串的右边界超过了maxpos,更新maxpos和index
结果提取
int maxlen = 0;
for(int i = 0; i < 2 * n + 2; i++)
maxlen = max(maxlen, mp[i] - 1);
return maxlen;
mp[i] - 1得到的是原始字符串中的实际回文长度
遍历所有位置,找出最大的回文长度
为什么这个算法有效?
对称性利用:当一个回文串包含在另一个更大的回文串中时,它的对称点也会有相似的回文性质。
线性时间:每个字符最多被比较两次(一次在确定初始半径时,一次在扩展时),所以时间复杂度是O(n)。