Manacher算法

本文介绍Manacher算法,一种高效寻找字符串中最长回文子串的方法。时间与空间复杂度均为O(n),适用于需要快速处理大量数据的应用场景。文章通过实例详细讲解了Manacher算法的工作原理,并对比了动态规划及中心搜索等其他解法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Manacher算法


Manacher算法求最长回文子串,时间和空间复杂度都是O(n),难度比较大又比较难记,这里写一下备忘。


Leetcode 5.最长回文子串

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

示例1:

输入:s = "babad"
输出:"bab"
解释:"aba" 同样是符合题意的答案。

示例2:

输入:s = "cbbd"
输出:"bb"

提示:

  • 1 <= s.length <= 1000
  • s 仅由数字和英文字母组成

方法1:DP

class Solution {
    public String longestPalindrome(String s) {
        int len = s.length();
        if (len < 2) {
            return s;
        }
        // 记录下最长回文子串的下标和长度
        int begin = 0;
        int maxLen = 1;

        // 建立dp数组
        // dp[i][j]指从i到j的数组是否为回文串(包括i,j)
        boolean dp[][] = new boolean[len][len];
        // 长度为1的是回文串
        for (int i = 0; i < dp.length; i++) {
            dp[i][i] = true;
        }

        // 先枚举子串长度
        for (int length = 2; length <= len; length++) {
            // 再枚举左边界
            for (int i = 0; i < len; i++) {
                // 右边界
                int j = i + length - 1;
                if (j >= len) {
                    break;
                }
                if (s.charAt(i) != s.charAt(j)) {
                    dp[i][j] = false;
                } else {
                    if (j - i < 3) {
                        dp[i][j] = true;
                    } else {
                        dp[i][j] = dp[i+1][j-1];
                    }
                }
                if (dp[i][j] == true && j - i + 1 > maxLen) {
                    begin = i;
                    maxLen = j - i + 1;
                }
            }
        }
        return s.substring(begin, begin+maxLen);
    }
}

方法2:中心搜索

class Solution {
    public String longestPalindrome(String s) {
        if (s.length() <= 1) {
            return s;
        }
        int left = 0;
        int right = 0;
        for (int i = 0; i < s.length(); i++) {
            int len1 = currMax(s, i, i);
            int len2 = currMax(s, i, i+1);
            int len = Math.max(len1, len2);
            if (len > right - left + 1) {
                right = i + len / 2;
                left = right - len + 1;
            }
        }
        return s.substring(left, right+1);
    }

    public int currMax(String s, int i, int j) {
        while (i >= 0 && j < s.length() && s.charAt(i) == s.charAt(j)) {
            i--;
            j++;
        }
        return j - i - 1;
    }
}

方法3:Manacher算法

Manacher变量示意图
请添加图片描述

class Solution {
    // Manacher
    public String longestPalindrome(String s) {
        if (s.length() <= 1) {
            return s;
        }
        // 1、数据预处理:把所有字符串添加#
        String str = this.addDividers(s, '#');
        int len = str.length();

        // 臂长数组
        int[] p = new int[len];
        int center = 0;
        int maxRight = 0;
        p[1] = 1;

        // 对于str而言的
        int begin = 0;
        int maxLen = 1;

        // 对于4种情况,可以优化为两种
        for (int i = 0; i < len; i++) {
            // 情况2的(1)(3)优化为一个min
            if (i < maxRight) {
                int mirror = 2 * center - i;
                p[i] = Math.min(p[mirror], maxRight - i);
            }

            // 情况1和情况2的(2)都需要再扩散
            // 这里顺便把情况2的(1)(3)也拿去扩散了,但是不影响,因为扩散结果一定还在原地踏步
            p[i] = expand(str, i - p[i], i + p[i]);
            
            // 如果maxRight变化,center也要跟着变化
            if (i + p[i] > maxRight) {
                maxRight = i + p[i];
                center = i;
            }
			
            // 记录最大的回文子串的长度和开始的下标
            if (2 * p[i] + 1 > maxLen) {
                maxLen = 2 * p[i] + 1;
                begin = i - p[i];
            }
        }
        return delDividers(str, begin, maxLen, '#');
    }

    // 1、数据预处理:把所有字符串添加#
    public String addDividers(String s, char ch) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < s.length(); i++) {
            sb.append(ch);
            sb.append(s.charAt(i));
        }
        sb.append(ch);
        return sb.toString();
    }

    // 2、中心扩展算法,返回臂长
    public int expand(String s, int left, int right) {
        while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {
            left--;
            right++;
        }
        return (right - left - 2) / 2;
    }

    // 3、数据后处理:把所有字符串减去#
    public String delDividers(String s, int left, int len, char ch) {
        StringBuilder sb = new StringBuilder();
        for (int i = left; i < left + len; i++) {
            if (s.charAt(i) != '#') {
                sb.append(s.charAt(i));
            }
        }
        return sb.toString();
    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值