• 欢迎访问搞代码网站,推荐使用最新版火狐浏览器和Chrome浏览器访问本网站!
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏搞代码吧

关于java:马拉车算法其实并不难

java 搞代码 4年前 (2022-01-27) 40次浏览 已收录 0个评论
文章目录[隐藏]

要说马拉车算法,必须说说这道题,查找最长回文子串,马拉车算法是其中一种解法,狠人话不多,间接往下看:

题目形容

给你一个字符串 s,找到 s 中最长的回文子串。

例子

<code class="txt">示例 1:
输出:s = "babad"
输入:"bab"
解释:"aba" 同样是合乎题意的答案。

示例 2:
输出:s = "cbbd"
输入:"bb"

示例 3:
输出:s = "a"
输入:"a"

示例 4:
输出:s = "ac"
输入:"a"

马拉车算法

这是一个微妙的算法,是1957年一个叫Manacher的人创造的,所以叫Manacher‘s Algorithm,次要是用来查找一个字符串的最长回文子串,这个算法最大的奉献是将工夫复杂度晋升到线性,后面咱们说的动静布局的工夫复杂度为 O(n2)。

后面说的核心拓展法,核心可能是字符也可能是字符的间隙,这样如果有 n 个字符,就有 n+n+1 个核心:

为了解决下面说的核心可能是间隙的问题,咱们往每个字符间隙插入”#“,为了让拓展完结边界更加清晰,右边的边界插入”^“,左边的边界插入 “$“:

S 示意插入”#“,”^“,”$“等符号之后的字符串,咱们用一个数组P示意S中每一个字符可能往两边拓展的长度:

比方 P[8] = 3,示意能够往两边别离拓展3个字符,也就是回文串的长度为 3,去掉 # 之后的字符串为aca

P[11]= 4,示意能够往两边别离拓展4个字符,也就是回文串的长度为 4,去掉 # 之后的字符串为caac

假如咱们曾经得悉数组P,那么咱们怎么失去回文串?

P 的下标 index ,减去 P[i](也就是回文串的长度),能够失去回文串结尾字符在拓展后的字符串 S 中的下标,除以2,就能够失去在原字符串中的下标了。

那么当初的问题是:如何求解数组P[i]

其实,马拉车算法的要害是:它充分利用了回文串的对称性,用已有的后果来帮忙计算后续的后果。

假如曾经计算出字符索引地位 P 的最大回文串,左边界是PL,右边界是PR

那么当咱们求因为一个地位 i 的时候,i 小于等于 PR,其实咱们能够找到 i 对于 P 的对称点 j:

那么假如 j 为核心的最长回文串长度为 len,并且在 PL 到 P 的范畴内,则 i 为核心的最长回文串也是如此:

以 i 为核心的最长回文子串长度等于以 j 为核心的最长回文子串的长度

然而这里有两个问题:

  • 前一个回文字符串P,是哪一个?
  • 有哪些非凡状况?非凡状况怎么解决?

(1) 前一个回文字符串 P,是指的后面计算出来的右边界最靠右的回文串,因为这样它最可能笼罩咱们当初要计算的 i 为核心的索引,能够尽量重用之前的后果的对称性。

也正因为如此,咱们在计算的时候,须要一直保留更新 P 的核心和右边界,用于每一次计算。

(2) 非凡状况其实就是以后 i 的最长回文字符串计算不能再利用 P 点的对称,例如:

  1. i 的回文串的右边界超出了 P 的右边界 PR:

这种状况的解决方案是:超过的局部,须要依照核心拓展法来一一拓展。

  1. i 不在 以 P 为核心的回文串外面,只能依照核心拓展法来解决。

具体的代码实现如下:

<code class="java">    // 结构字符串
    public String preProcess(String s) {
        int n = s.length();
        if (n == 0) {
            return "^$";
        }
        String ret = "^";
        for (int i = 0; i < n; i++)
            ret = ret + "#" + s.charAt(i);
        ret = ret + "#$";
        return ret;
    }

    // 马拉车算法
    public String longestPalindrome(String str) {
        String S = preProcess(str);
        int n = S.length();
        // 保留回文串的长度
        int[] P = new int[n];
        // 保留边界最右的回文核心以及右边界
        int center = 0, right = 0;
        // 从第 1 个字符开始
        for (int i = 1; i < n - 1; i++) {
            // 找出i对于后面核心的对称
            int mirror = 2 * center - i;
            if (right > i) {
                // i 在右边界的范畴内,看看i的对称点的回文串长度,以及i到右边界的长度,取两个较小的那个
                // 不能溢出之前的边界,否则就得核心拓展
                P[i] = Math.min(right - i, P[mirror]);
            } else {
                // 超过范畴了,核心拓展
                P[i] = 0;
            }

            // 核心拓展
            while (S.charAt(i + 1 + P[i]) == S.charAt(i - 1 - P[i])) {
                P[i]++;
            }

            // 看看新的索引是不是比之前保留的最右边界的回文串还要靠右
            if (i + P[i] > right) {
                // 更新核心
                center = i;
                // 更新右边界
                right = i + P[i];
            }

        }

        // 通过回文长度数组找出最长的回文串
        int maxLen = 0;
        int centerIndex = 0;
        for (int i = 1; i < n - 1; i++) {
            if (P[i] > maxLen) {
                maxLen = P[i];
                centerIndex = i;
            }
        }
        int start = (centerIndex - maxLen) / 2;
        return str.substring(start, start + maxLen);
    }

至于算法的复杂度,空间复杂度借助了大小为n的数组,为O(n),而工夫复杂度,看似是用了两层循环,实则不是 O(n2),而是 O(n),因为绝大多数索引地位会间接利用后面的后果以及对称性取得后果,常数次就能够失去后果,而那些须要核心拓展的,是因为超出后面后果笼罩的范畴,才须要拓展,拓展所得的后果,有利于下一个索引地位的计算,因而拓展实际上较少。

【作者简介】
秦怀,公众号【秦怀杂货店】作者,技术之路不在一时,山高水长,纵使迟缓,驰而不息。集体写作方向:Java源码解析JDBCMybatisSpringredis分布式剑指OfferLeetCode等,认真写好每一篇文章,不喜爱题目党,不喜爱花里胡哨,大多写系列文章,不能保障我写的都完全正确,然而我保障所写的均通过实际或者查找材料。脱漏或者谬误之处,还望斧正。

剑指Offer全副题解PDF

2020年我写了什么?

开源编程笔记


搞代码网(gaodaima.com)提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发送到邮箱[email protected],我们会在看到邮件的第一时间内为您处理,或直接联系QQ:872152909。本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:关于java:马拉车算法其实并不难

喜欢 (0)
[搞代码]
分享 (0)
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址