最长回文子串算法Manacher详解

最长回文子串算法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)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值