寻找最长回文子串——manacher算法

1. Manacher算法的基本概念

什么是Manacher算法?

Manacher算法是一种用于在 线性时间复杂度(O(n)O(n))内求解字符串中最长回文子串的算法。它的核心思想是通过 对称性动态规划 来避免重复计算。

为什么需要Manacher算法?(优化时间)
  • 暴力解法:枚举所有子串并检查是否为回文,时间复杂度为 O(n2)O(n2)。
  • Manacher算法:通过利用回文的对称性,将时间复杂度优化到 O(n)O(n)。

2. Manacher算法的核心思路(图)

       1. p[i]表示以第i个字符的最大回文半径。

       2. 当遍历到当前位置,因为i = 3的回文半径将当前位置包裹起来的,所以i = 5的回文半径至少是1,这个可有i = 1直接得到,i = 1与i = 5是关于i = 3镜像对称,此后对i = 5的位置继续向外暴力扩展,求得最终p[i]。

        3.对每个位置遍历完都得比较记录最大的半径的位置,和最右边被包裹的最大位置,以方便后续持续使用镜像直接得值。

       如上遍历情况,所以最终可得划线的答案依次为:0,3,0,1,0。

3. Manacher算法核心思路(代码处理)

预处理(统一处理为奇数个字符串)

为了统一处理奇数和偶数长度的回文子串,Manacher算法首先对字符串进行预处理:

  • 在字符串的每个字符之间插入一个特殊字符(如 #),并在字符串的开头和结尾也插入特殊字符。
  • 例如,字符串 abba 预处理后变为 #a#b#b#a#
定义数组
  • p[i] 表示以字符 s[i] 为中心的最长回文半径(包括中心字符)。
  • 例如,对于字符串 #a#b#b#a#p[3] = 4(回文子串为 #a#b#b#a#)。
利用对称性
  • C 为当前已知的最长回文子串的中心,R 为其右边界。
  • 对于当前字符 s[i],如果 i < R,则可以利用对称性快速计算 p[i]
状态转移
  • 如果 i < R,则 p[i] = min(R - i, p[2 * C - i])
  • 否则,从 i 开始向左右扩展,计算 p[i]

4. Manacher算法的代码实现

以下是 Manacher算法 的实现(Java):

public class Manacher {

    // Manacher算法
    public static String longestPalindrome(String s) {
        if (s == null || s.length() == 0) return "";

        // 预处理字符串
        StringBuilder sb = new StringBuilder();
        sb.append('^'); // 开头特殊字符
        for (char c : s.toCharArray()) {
            sb.append('#').append(c);
        }
        sb.append("#$"); // 结尾特殊字符
        String t = sb.toString();

        int n = t.length();
        int[] p = new int[n]; // p[i] 表示以 t[i] 为中心的最长回文半径
        int C = 0, R = 0; // C 是中心,R 是右边界

        for (int i = 1; i < n - 1; i++) {
            // 利用对称性快速计算 p[i]
            int mirror = 2 * C - i;
            if (i < R) {
                p[i] = Math.min(R - i, p[mirror]);
            }

            // 尝试扩展
            while (t.charAt(i + p[i] + 1) == t.charAt(i - p[i] - 1)) {
                p[i]++;
            }

            // 更新中心和右边界
            if (i + p[i] > R) {
                C = i;
                R = i + p[i];
            }
        }

        // 找到最长回文子串
        int maxLen = 0, center = 0;
        for (int i = 1; i < n - 1; i++) {
            if (p[i] > maxLen) {
                maxLen = p[i];
                center = i;
            }
        }

        // 提取最长回文子串
        int start = (center - maxLen) / 2;
        return s.substring(start, start + maxLen);
    }

    public static void main(String[] args) {
        String s = "babad";
        String result = longestPalindrome(s);
        System.out.println("最长回文子串: " + result); // 输出:bab 或 aba
    }
}

最长回文子串是指一个字符串中的最长回文子串。在 Java 中,可以使用动态规划的方法来解决这个问题。 首先,定义一个二维数组 dp[][],其中 dp[i][j] 表示字符串从索引 i 到 j 的子串是否是回文子串。初始化时,将所有 dp[i][i] 设置为 true,表示单个字符是回文子串。 然后,通过遍历字符串的长度和起始索引来更新 dp[][] 数组。具体的更新规则如下: 1. 当 s.charAt(i) == s.charAt(j) 且 (j - i <= 2 || dp[i+1][j-1] == true) 时,dp[i][j] 也为 true。这意味着如果当前字符相等,并且子串长度小于等于2或者去掉首尾字符后的子串是回文子串,则当前子串也是回文子串。 2. 在更新 dp[][] 数组的过程中,记录最长回文子串的起始索引和长度。 以下是使用动态规划解决最长回文子串问题的 Java 代码示例: ```java public class LongestPalindromeSubstring { public static String longestPalindrome(String s) { int n = s.length(); boolean[][] dp = new boolean[n][n]; int maxLength = 1; int start = 0; // 初始化单个字符为回文子串 for (int i = 0; i < n; i++) { dp[i][i] = true; } // 更新 dp[][] 数组并记录最长回文子串的起始索引和长度 for (int len = 2; len <= n; len++) { for (int i = 0; i <= n - len; i++) { int j = i + len - 1; if (s.charAt(i) == s.charAt(j) && (len <= 2 || dp[i+1][j-1])) { dp[i][j] = true; if (len > maxLength) { maxLength = len; start = i; } } } } return s.substring(start, start + maxLength); } public static void main(String[] args) { String s = "babad"; String longestPalindrome = longestPalindrome(s); System.out.println("最长回文子串为: " + longestPalindrome); } } ``` 运行上述代码,输出结果为: ``` 最长回文子串为: bab ``` 以上就是使用动态规划解决最长回文子串问题的 Java 实现方法。希望能对你有所帮助!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值