leetcode刷题(52)——5. 最长回文子串

一、题目

给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。

示例 1:

输入: "babad"
输出: "bab"
注意: "aba" 也是一个有效答案。

示例 2:

输入: "cbbd"
输出: "bb"

二、思路及代码实现

思路一:中心扩展法

若回文串的长度为奇数,则回文串的中心位置是字符;若回文串的长度为偶数,则回文串的中心位置位于两个字符的中间。所以,我们遍历字符串,每次选择一个中心,进行左右扩展,找到所有的回文串并记录最长回文串。

参考代码:

class Solution {
    public String longestPalindrome(String s) {
        if(s.length() == 0 || s == null)
            return "";
        int left = 0;// 回文串的左边界
        int right = 0;// 回文串的右边界
        for(int i = 0; i < s.length(); i++){
            int odd = expandAroundCenter(s, i, i);// 奇数长度的回文串
            int even = expandAroundCenter(s, i, i + 1);// 偶数长度的回文串
            // 更新每个回文串的left和right,记录最长回文串的长度
            int maxLength = Math.max(odd, even);
            if(maxLength > right - left){
                left = i - (maxLength - 1) / 2;
                right = i + maxLength / 2;
            }
        }
        return s.substring(left, right + 1);
    }

    // 以left和right为中心向两边扩展;
    // 初始left = right时,找到奇数长度的回文串;初始left = i,right = i + 1时,找到偶数长度的回文串。
    // 最后返回回文串的长度
    public int expandAroundCenter(String s, int left, int right){
        int l = left;
        int r = right;
        while(l >= 0 && r < s.length() && s.charAt(l) == s.charAt(r)){
            l--;
            r++;
        }
        return r - l - 1;
    }
}

思路二:动态规划

动态规划的核心在于记录子问题的状态,省掉重复计算。所以我们可以在中心扩展法的基础上,使用动态规划来解决问题。

  • 问题拆解:
    对于一个回文串,两边同时缩短,得到的也是回文串。如,“abdba”,去掉两边的 ‘a’ 之后得到 “bdb” 还是回文串。设 s [ i , j ] s[i, j] s[i,j] 表示一段字符串(i 为左边界,j 为右边界),假设由中心扩展法已经找到一个回文串 s [ i , j ] s[i, j] s[i,j] ,再继续向两边扩展,如果 s [ i − 1 ] = s [ j + 1 ] s[i - 1] = s[j +1] s[i1]=s[j+1], 则 s [ i − 1 , j + 1 ] s[i - 1, j +1] s[i1,j+1] 也是一个回文串。这也就是原问题和子问题的联系。
  • 定义状态:
    根据上面的分析,我们定义状态为:dp[i][j] 表示字符串 s[i, j] 是不是回文串。所以我们创建布尔型的数组来存储状态,dp[i][j] = true 表示字符串 s[i, j] 是回文串;dp[i][j] = false 表示字符串 s[i, j] 不是回文串。
  • 推导状态转移方程:
    由问题拆解我们知道:若 s [ i + 1 , j − 1 ] s[i + 1, j -1] s[i+1,j1] 是一个回文串,且 s [ i ] = s [ j ] s[i] = s[j] s[i]=s[j],则 s [ i , j ] s[i, j] s[i,j] 也是一个回文串,用公式表示为
    d p [ i ] [ j ] = ( s [ i ] = = s [ j ] ) & & d p [ i + 1 ] [ j − 1 ] dp[i][j] = (s[i] == s[j]) \quad \&\& \quad dp[i +1][j - 1] dp[i][j]=(s[i]==s[j])&&dp[i+1][j1]
  • 寻找边界条件:
    (1)因为单个字符肯定是回文串,所以有 d p [ i ] [ i ] = t r u e dp[i][i] = true dp[i][i]=true
    (2)对于 d p [ i ] [ j ] = ( s [ i ] = = s [ j ] ) & & d p [ i + 1 ] [ j − 1 ] dp[i][j] = (s[i] == s[j]) \quad \&\& \quad dp[i +1][j - 1] dp[i][j]=(s[i]==s[j])&&dp[i+1][j1] 如果字符串的长度小于等于 3,我们只需要判断 s[i] == s[j] 就能知道 s [ i , j ] s[i ,j] s[i,j] 是不是回文串,如 “aba”,“aa”。所以,当 s [ i ] = s [ j ] s[i] = s[j] s[i]=s[j] j − i < 3 j - i < 3 ji<3 时, d p [ i ] [ j ] = t r u e dp[i][j] = true dp[i][j]=true;而当 s [ i ] = s [ j ] s[i] = s[j] s[i]=s[j] j − i ≥ 3 j - i \geq 3 ji3 时,执行状态转移方程。这样也可以省掉很多计算。

参考代码:

class Solution {
    public String longestPalindrome(String s) {
        int length = s.length();
        if(length < 2)
            return s;
        boolean[][] dp = new boolean[length][length];
        // 初始化:这就相当于已经处理了长度为 1 的回文串,后面只需要处理长度大于 1 的回文串
        for(int i = 0; i < length; i++){
            dp[i][i] = true;
        }
        int maxLength = 1;// 记录最长回文串的长度
        int left = 0;
        for(int j = 1; j < length; j++){
            for(int i = 0; i < j; i++){
                if(s.charAt(i) == s.charAt(j)){
                    if(j - i < 3)
                        dp[i][j] = true;
                    else
                        dp[i][j] = dp[i + 1][j - 1];// 状态转移方程
                }else{
                    dp[i][j] = false;
                }
                // 若dp[i][j] = true,说明s[i, j]是回文串,记录回文串的长度和左边界。
                if(dp[i][j]){
                    int curLength = j - i + 1;
                    if(curLength > maxLength){
                        maxLength = curLength;// 更新回文串最大长度
                        left = i;// 左边界
                    }
                }
            }
            
        }
        return s.substring(left, left + maxLength);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值