【算法】Manacher算法
阅读原文时间:2023年07月08日阅读:2

manacher算法是用来求解最长回文串的问题。最长回文串的解法一般有暴力法、动态规划、中心扩展法和manacher算法。

  • 暴力法的时间复杂度为\(O(n^3)\),一般都会超时;

  • 动态规划的时间复杂度和空间复杂度均为\(O(n^2)\),通过矩阵压缩存储,空间复杂度常数可以降低为0.5,但时间复杂度较高,基本不能再优化;

  • 中心扩展法在性能上优于动态规划,空间复杂度为\(O(1)\),但时间复杂度仍然是\(O(n^2)\);

  • manacher性能最好,时间复杂度和空间复杂度均为\(O(n)\)

    //中心扩展法的代码
    class Solution {
    public:
    string longestPalindrome(string s) {
    if(s.size() == 0)return "";
    int maxlen = 1, start = 0;
    for(int i = 0; i < s.size(); ++i){ int len1 = expand(s, i, i); int len2 = expand(s, i, i + 1); int len = max(len1, len2); if(len > maxlen){
    start = i - (len - 1) / 2;
    maxlen = len;
    }
    }
    return s.substr(start, maxlen);
    }
    private : int expand(const string &s, int l, int r){
    while(l >= 0 && r < s.size() && s[l] == s[r]){
    l--;
    r++;
    }
    return r - l - 1;
    }
    };

中心扩展法造成时间复杂度高的原因主要有两个方面:

  • 需要分奇数和偶数两种情况讨论;
  • 子串包含重复计算;

第一点从代码可以看出,第二点见插图:

对第 \(j\)个字符进行中心扩展时, 子串\(s[0, j - 1]\)都已经进行了中心扩展,以每个字符为中心的回文串信息都已经获得,在这些回文串中必然存在一个最长的回文串\(s[mx, my]\),其对称中心记作\(id\)。如果\(j < my\),则\(j\)一定有一个对称位置\(i\),假设以\(i\)为中心的回文串为左侧绿色的子串,则以\(j\)为中心的绿色子串一定也是回文串。但是中心扩展法忽略了这点,对这部分子串进行了重复比对。

manacher算法主要是对中心扩展法的两方面不足进行改进。

字符串预处理

为了不区分奇数和偶数两种情况,manacher对字符串进行了预处理,在长度为\(n\)的字符串的空隙中填入\(n+1\)相同的字符,使字符串的总长度变为\(2n + 1\)。

例如:

对于字符串abbac,处理之后为#a#b#b#a#c#(假设插入的字符为#

处理之后的字符串与原字符串的映射关系为:\(s[i] = temps[2 * i + 1]\)

\(i\)号位置之前有 \(i+1\)个gap

处理后的最大回文串长度与原来的长度的关系:\((tempLmax - 1) / 2 = lmax\)

算法实现

  • 数据结构

    • 回文半径数组\(radius[len]\)

      \(radius[i] = (tempLmax(i) - 1) / 2\),含义为字符\(temps[i]\)右侧的字符个数(不懂网上很多版本为什么带上\(temps[i]\))

    • 最大覆盖范围\((id, mx)\),\(id\)为对称中心

  • 算法实现

    • 每次在进行中心扩展时,先计算一个合适的扩展起点,而不是直接从当前位置直接扩展
    • 扩展的方法同中心扩展法相同
    • 每次扩展完毕,要更新最大覆盖范围\((id, mx)\)和最大长度

    string longestPalindrome(string s) {
    if(s.size() == 0)return "";
    string temps = "#";
    for(int i = 0; i < s.size(); ++i){ temps += s[i]; temps += '#'; } int len = temps.size(); int radius[len] = {0}; int id = 0, mx = 0; int start = 0, maxlen = 0; for(int i = 1; i < len; ++i){ if(i < mx){ radius[i] = min(radius[2 * id - i], mx - i); } for(int dl = radius[i] + 1; i - dl >= 0 && i + dl < len; ++dl){ if(temps[i - dl] == temps[i + dl])radius[i]++; else break; } if(radius[i] + i > mx){
    id = i;
    mx = radius[i] + i;
    }
    if(radius[i] > maxlen){
    start = (i - radius[i]) / 2;
    maxlen = radius[i];
    }
    }
    return s.substr(start, maxlen);
    }

更详细的的介绍

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器

你可能感兴趣的文章