【最长回文子串]

给你一个字符串 s,找到 s 中最长的回文子串。
回文子串: 字符串中连续的非空回文子串字符序列。
回文性: 如果字符串向前和向后读都相同,则它满足回文性。
示例 1:
输入:s = “babad”
输出:“bab”
解释:“aba” 同样是符合题意的答案。
示例 2:
输入:s = “cbbd”
输出:“bb”

动态规划

这段代码实现了一个寻找字符串中最长回文子串的算法,使用的是动态规划(Dynamic Programming)的方法。我们一步步解释其原理和实现思路。

1. 问题定义

回文串是指一个字符串从前往后读和从后往前读是一样的。例如,字符串 “aba” 和 “racecar” 都是回文串。给定一个字符串 s,我们要找到它的最长回文子串。

2. 思路分析

动态规划的核心思想是通过构建一个表格(或数组)来记录中间状态,从而避免重复计算。

2.1 表格定义

我们定义一个二维数组 dp,其中 dp[i][j] 表示字符串 s 的第 i 到第 j 个字符组成的子串 s[i…j] 是否为回文串。
如果 s[i…j] 是回文串,那么 dp[i][j] = true。
如果 s[i…j] 不是回文串,那么 dp[i][j] = false。

2.2 状态转移方程

如何判断 s[i…j] 是否为回文串呢?
首先,如果 s[i] == s[j],即首尾字符相同,那么我们需要继续检查子串 s[i+1…j-1] 是否为回文串。
如果 s[i+1…j-1] 是回文串,那么 s[i…j] 也是回文串,即 dp[i][j] = true。
否则,s[i…j] 不是回文串,即 dp[i][j] = false。
总结起来,可以写成:
在这里插入图片描述

2.3 边界条件

所有长度为1的子串都是回文串,因此 dp[i][i] = true。
对于长度为2的子串,如果两个字符相等,则它是回文串。

2.4 求解过程

通过动态规划的思想,我们从较短的子串开始,一步步构建更长的子串的回文信息。最终,我们就可以得到最长的回文子串。

  public String longestPalindrome(String s) {
        if (s == null || s.length() < 2) {
            return s;
        }
        int n = s.length();
        // dp[i][j] 表示 s[i...j] 是否为回文
        boolean[][] dp = new boolean[n][n];
        // 记录最长回文子串的起始位置
        int start = 0;
        // 记录最长回文子串的长度
        int maxLength = 1;
        // 所有长度为1的子串都是回文
        for (int i = 0; i < n; i++) {
            dp[i][i] = true;
        }
        // 检查长度为2的子串
        for (int i = 0; i < n - 1; i++) {
            if (s.charAt(i) == s.charAt(i + 1)) {
                dp[i][i + 1] = true;
                start = i;
                maxLength = 2;
            }
        }
        // 检查长度大于2的子串
        // length 表示子串的长度
        for (int length = 3; length <= n; length++) {
            for (int i = 0; i <= n - length; i++) {
                // 子串的结束索引
                int j = i + length - 1;
                // 如果首尾字符相同,并且内部子串也是回文
                if (s.charAt(i) == s.charAt(j) && dp[i + 1][j - 1]) {
                    dp[i][j] = true;
                    start = i;
                    maxLength = length;
                }
            }
        }
        return s.substring(start, start + maxLength);
    }

中心扩展法

1. 问题定义

与之前一样,问题是寻找字符串 s 中的最长回文子串。

2. 思路分析

回文串有一个特点,即它是关于中心对称的。因此,我们可以选择一个位置作为回文中心,向两边扩展,直到不能再扩展为止,这样就可以找到以这个位置为中心的最长回文。
需要注意的是,回文的中心可以是一个字符(奇数长度的回文)或者两个字符之间的间隙(偶数长度的回文)。

3. 解法步骤

3.1 中心扩展

对于字符串中的每一个字符(或字符间隙),都可以作为回文中心,然后从这个中心向外扩展来寻找回文。通过对每个可能的中心进行扩展,我们可以找到最长的回文子串。

奇数长度的回文: 以某个字符为中心,从它向左右两边扩展。
偶数长度的回文: 以两个字符之间的间隙为中心,从这个间隙向左右两边扩展。

代码实现:

public String longestPalindrome(String s) {
    if (s == null || s.isEmpty()) {
        return "";
    }
    int start = 0, maxLength = 0;
    for (int i = 0; i < s.length(); i++) {
        // 检查奇数长度的回文(中心是单个字符)
        int len1 = expandAroundCenter(s, i, i);
        // 检查偶数长度的回文(中心是两个字符之间的间隙)
        int len2 = expandAroundCenter(s, i, i + 1);
        // 获取当前最大回文长度
        int len = Math.max(len1, len2);
        if (len > maxLength) {
            maxLength = len;
            start = i - (len - 1) / 2;
        }
    }
    return s.substring(start, start + maxLength);
}

private int expandAroundCenter(String s, int left, int right) {
    while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {
        left--;
        right++;
    }
    return right - left - 1;
}

详细步骤解析

检查输入有效性:

首先检查字符串是否为空或长度为0,如果是,则直接返回空字符串。
遍历每个可能的中心: 使用 for 循环遍历字符串中的每个字符 i,将它作为中心进行扩展。
调用 expandAroundCenter 函数检查奇数长度的回文(以 i 为中心)。
再调用一次 expandAroundCenter 函数检查偶数长度的回文(以 i 和 i+1 之间的间隙为中心)。
更新最长回文信息: 通过比较当前扩展得到的回文长度,如果发现更长的回文,则更新最长回文的起始位置和长度。
返回结果: 最后返回找到的最长回文子串。

5. 时间复杂度和空间复杂度

时间复杂度: 每次扩展的操作是 O(n),总共进行 n 次中心扩展,因此时间复杂度为 O(n^2)。
空间复杂度: 只使用了常数级别的额外空间,因此空间复杂度为 O(1)。

6. 总结

这种方法利用了回文的对称性,通过从每个可能的中心向外扩展,简单且有效地找到了最长的回文子串。与动态规划方法相比,代码更为简洁,并且由于不需要额外的二维数组,空间复杂度也更低

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值