美文网首页
Manacher算法

Manacher算法

作者: topshi | 来源:发表于2019-06-12 16:17 被阅读0次

前言

Manacher算法用于求解字符串中最长的回文子串,它和KMP算法的改进方式很类似,都是基于暴力求解的方法,通过利用历史信息进行优化。所谓回文子串是指以子串中间字符为中心,左右两端是对称的。例如,cbcacb的最长回文子串为bcacb,其长度为5。
相关题目:5. 最长回文子串

最长回文子串的暴力求解

  • 遍历字符串中的每个字符,比较其左右关于该字符为中心对称的位置的字符是否相同,如果相同比较该字符的左边的左边和右边的右边字符是否相同,以这种向外“扩”的方式计算以每个字符为中心的回文子串长度。



    暴力求解的时间复杂度是O(n * n)

Manacher算法

Manacher算法基于暴力求解做出优化,它定义了如下几个概念:

  • 回文半径:以某个字符为中心向外扩的字符个数称为字符的回文直径,那么回文半径不言而喻。例如cbcacb,以字符a为中心向外扩,可以扩c,再扩b,再加上字符本身,字符a的回文半径是3。
  • 回文半径数组pArr:记录以每个字符为中心的回文半径。例如上图的回文半径数组为[1,2,1,3,1,1].
  • 最右回文右边界R:向外扩的过程中,扩到的最右字符的下标。例如字符串cbcacb,遍历到第一个c,其只能扩到自己,则R = 0;遍历到b时,可以扩一个c,则R = 2.(一旦能扩到更右的字符,必须更新R
  • 最右回文右边界对应的回文中心CCR是对应的,R正是中心C所能扩到的最右字符下标,因此RC是同时更新的。

我们的“扩”是基于一个中心向外扩的,扩出来的回文子串长度必是奇数。为了处理回文子串为偶数的情况,我们在字符间插入一个字符#以保证回文子串长度都是奇数。例如,字符串cbcacb - > #c#b#c#a#c#b#

Manacher算法如何利用pArrCR对查找进行加速

我们以指针cur去遍历字符串,

  • 情况1:
    cur位于R的右边时,这种情况无法使用回文半径数组pArr进行加速,只能暴力向外扩。
  • 情况2:
    cur位于R的左边时,此时可以利用回文半径数组pArr来进行加速处理。
    首先,画出Rcur关于中心C的对称点,
    • 如果以cur'为中心的回文子串的左边界没有超出L(也没有压线),如下图

      那么,pArr[cur] = pArr[cur']
      证明:首先,L -> CC -> R这两块是对称的,身为子块的x' -> y'的对称块为y -> x,所以pArr[cur] = pArr[cur']
      疑问:pArr[cur]有没有可能比pArr[cur']大?不可能,假设y前面的一个字符 == x后面的一个字符,那当时扩cur'时,为什么不扩,它们是对称的。
    • 如果以cur'为中心的回文子串的左边界超出了L,如下图

      那么,pArr[cur] = R - cur + 1
      证明:假设R的右边一个字符为x,它的对称字符为x',根据pArr[C]x' != x;又x' == y'y' == y,推出x != y。因此,以cur为中心的回文半径为R - cur + 1
    • 如果以cur'为中心的回文子串的左边界刚好压中L,如下图

      这种情况下,y的前一个字符和x的后一个字符的关系无法通过历史信息推算出来,只知道以cur为中心的回文子串半径至少为R - cur + 1,因此需要继续向外扩以确定pArr[cur]

综上所述,分四种情况:

  • curR右边,暴力扩
  • curR左边
    • cur'回文左边界没超过LpArr[cur] = pArr[cur']
    • cur'回文左边界超出LpArr[cur] = R - cur + 1
    • cur'回文左边界压中LpArr[cur] = R - cur + 1 + 继续向外扩

时间复杂度

  • 遍历了一遍字符串,O(n)

相关题目

最长回文子串

class Solution {
    public String longestPalindrome(String s) {
        if(s.length() < 2) return s;
        char[] m = manacherString(s);
        int[] pArr = new int[m.length];
        int c = -1;
        int r = -1;
        int maxLen = -1;
        StringBuilder maxStr = new StringBuilder();
        for(int i = 0;i != m.length;i++){
            //i在r右边,直接1;否则就是pArr[cur']或R - cur + 1
            pArr[i] = i < r ? Math.min(pArr[2 * c - i], r - i + 1) : 1;
            //看能不能扩(针对压中L和暴力扩的情况)
            while(i - pArr[i] >= 0 && i + pArr[i] < m.length
                  && m[i - pArr[i]] == m[i + pArr[i]]){
                pArr[i]++;
            }
            if(i + pArr[i]- 1 > r){
                c = i;
                r = i + pArr[i] - 1;
            }
            if(maxLen <= pArr[i]){
                maxLen = pArr[i];
                maxStr.delete(0,maxStr.length());
                for(int j = i - pArr[i]+1;j < r;j++){
                    if(m[j] != '#'){
                        maxStr.append(m[j]);
                    }
                }
            }
        }
        return maxStr.toString();
    }
    private char[] manacherString(String s){
        char[] m = new char[2 * s.length() + 1];
        for(int i = 0;i < m.length;i++){
            m[i] = i % 2 == 0? '#' : s.charAt(i / 2);
        }
        return m;
    }
}

相关文章

  • Manacher算法(马拉车算法)

    Manacher算法(马拉车算法) Manacher算法,又叫“马拉车”算法,可以在时间复杂度为O(n)的情况下求...

  • 寻找字符串中的最长回文子串Manacher's Algo

    Manacher's Algorithm 马拉车算法

  • manacher算法

    概念:求字符串的最大回文串1.先处理成偶数串2.回文半径3.回文半径最右边界,并记录最早中心位置 扩展题 给定一个...

  • Manacher算法

    最长回文子串问题# 给定一个字符串,找出最长的回文子串,如"waabwswbfd",则最长子串为bwsb. 中心试...

  • Manacher算法

    前言 Manacher算法用于求解字符串中最长的回文子串,它和KMP算法的改进方式很类似,都是基于暴力求解的方法,...

  • Manacher算法

    看这样一道例题: hdoj-3068.最长回文 给出一个只由小写英文字符a,b,c...y,z组成的字符串S,求S...

  • Manacher算法

    首先让我们来看Leetcode上的一道题。 题意解析:给定一个字符串S,求这个字符串的最长回文子串。所谓回文字符串...

  • Manacher算法

    Manacher又叫"马拉车"算法,它可以在O(N)的平均时间复杂度下面找到一个字符串的最长回文子串。 题目 Le...

  • Manacher算法

  • 马拉车算法(Manacher's Algorithm)

    Manacher's Algorithm,中文名叫马拉车算法,是一位名叫Manacher的人在1975年提出的一种...

网友评论

      本文标题:Manacher算法

      本文链接:https://www.haomeiwen.com/subject/ivmkfctx.html