0005-最长回文子串2022年04月12日

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

示例 1:

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

示例 2:

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

提示:

  • 1 <= s.length <= 1000
  • s 仅由数字和英文字母组成
/*
 * @lc app=leetcode.cn id=5 lang=java
 *
 * [5] 最长回文子串
 *
 * https://leetcode-cn.com/problems/longest-palindromic-substring/description/
 *
 * algorithms
 * Medium (36.34%)
 * Likes:    4959
 * Dislikes: 0
 * Total Accepted:    953.6K
 * Total Submissions: 2.6M
 * Testcase Example:  '"babad"'
 *
 * 给你一个字符串 s,找到 s 中最长的回文子串。
 * 
 * 
 * 
 * 示例 1:
 * 
 * 
 * 输入:s = "babad"
 * 输出:"bab"
 * 解释:"aba" 同样是符合题意的答案。
 * 
 * 
 * 示例 2:
 * 
 * 
 * 输入:s = "cbbd"
 * 输出:"bb"
 * 
 * 
 * 
 * 
 * 提示:
 * 
 * 
 * 1 <= s.length <= 1000
 * s 仅由数字和英文字母组成
 * 
 * 
 */

1. 暴力解法

class Solution {
    // 暴力求解
    public String longestPalindrome(String s) {
        int len=s.length();
        if(len < 2){
            return s;
        }
        // s.charAt(i) 每次都会检查数组下标是否越界,因此先转换成字符数组,这一步非必需
        char[] charArray = s.toCharArray();

        int maxLen = 1; // 如果没有长度超过1的最长回文子串,则最长回文子串长度为1
        int begin = 0;
        //枚举所有长度严格大于 1 的字串 charArray[i..j]
        for(int i=0;i<len-1;i++){
            for(int j=i+1;j<len;j++){
                // 遍历的子串长度 需要 超过当前最长的回文子串长度,减少遍历数量
                if(j-i+1 > maxLen && validPalindromic(charArray, i, j)){
                    maxLen = j-i+1;
                    begin = i;
                }
            }
        }
        return s.substring(begin, begin + maxLen); // exclusive endIndex
    }

    /**
     * 验证子串 s[left...right]是否为回文串
     */
    private boolean validPalindromic(char[] charArray,int left,int right){
        while(left < right){
            if(charArray[left]!=charArray[right]){
                return false;
            }
            left++;
            right--;
        }
        return true;
    }

}

2. 中心扩散法:

class Sulution{
    // 中心扩散
    /**
     * 中心扩散法:
     * 枚举所有可能的中心回文子串的中心位置
     * 中心位置可能是一个字符,也可能是两个相邻的字符
     * 记录最长回文子串的相关变量
     */
     public String longestPalindrome(String s) {
         int len = s.length();
         // 特殊判断
         if(len < 2){
             return s;
         }
         // 最长回文子串相关变量
         int maxLen=1;
         int begin=0;

         char[] charArray = s.toCharArray();
         // 由于最后位置的字符不能向右进行扩展了,
         //所以遍历枚举的时候 不需要枚举该位置,只需要枚举到len-2这个位置
         for(int i=0; i< len-1;i++){
             int oddLen = expandAroundCenter(charArray, i, i);
             int evenLen = expandAroundCenter(charArray, i, i+1);

             int curLen = Math.max(oddLen,evenLen);
             if(curLen > maxLen){
                 maxLen=curLen;
                 begin=i-(maxLen-1)/2;
             }
         }
         return s.substring(begin, begin+maxLen);
     }

     /**
      * 
      * @param charArray 原始字符串的字符数据
      * @param left 起始左边界(可以取到)
      * @param right 起始右边界(可以取到)
      * @return
      */
     private int expandAroundCenter(char[] charArray,int left,int right){
         int len = charArray.length;
         int i = left;
         int j = right;
         while(i >= 0 && j < len){
             if(charArray[i]==charArray[j]){
                 i--;
                 j++;
             }else{
                 break;
             }
         }
         // 跳出while循环时,恰好满足s.charAt(i)!=s.charAt(j)
         // 回文子串长度为 j - i + 1 - 2 = j - i - 1
         return j-i-1;
     }

}

3. 动态规划法:

基础夯实:java中 布尔类型的变量初始值默认为false。

动态规划,最重要的是得到状态转移方程,通过之前计算得到的结果装填,来计算得到当前需要求解的状态,而不是重新求解。

class Solution {

    /**
     * 动态规划:
     *  回文串天然具有状态转移性质的是,这是因为一个回文去掉两头以后,剩下的部分依然是回文
     * 
     * 状态: dp[i][j] 表示子串 s[i...j]是否为回文子串 ('[]') 表示左右闭区间
     * 得到 子串的转移状态方程 dp[i][j] = (s[i]==s[j]) and dp[i+1][j-1] (dp[i+1][j-1] 表示由于判断了首尾字符相等,所以去掉了头尾继续判断下一个字符)
     * 由于使用了下标进行访问,就一定要考虑到下标是否有效,就有了边界条件
     * j - 1 - (i+1) + 1 < 2 表示 下标j-1 字符和 下标i+1字符 之间不构成区间,
     * 此时 下标j-1 字符和 下标i+1字符 之间的字符数为1或0,所以不构成字符串区间,
     * 也就是这个范围的长度 严格小于2的时候,dp[i+1][j-1]没有意义,
     * 整理得 j - i < 3 => j - i + 1 < 4 
     * 这个表达式的语义就是 当s[i..j]的长度为2或者3时,就不用检查子串(s[i+1...j-1])是否回文了,就不需要进行状态转移了。
     * 
     * 动态规划实际上是在填写一张二维表格
     * 单个字符一定是回文串,所以首先可以把主对角线上的值都赋值为true
     * 初始化:  dp[i][i]=true (表示对角线,这部分可以不进行)
     * 输出: 在得到一个状态的值为true的时候,记录起始位置和长度,填表完成以后再截取 (填表为:填充dp矩阵)
     * 
     * i <= j
     * 状态转移方程: dp[i][j] = (s[i]==s[j]) and (j - i < 3 or dp[i+1][j-1])
     * 
     * 由于 dp[i][j] 参考它左下方的值 dp[i+1][j-1]:
     * (1) 先升序填列;
     * (2) 再升序填行;
     * 
     * */ 
    public String longestPalindrome(String s){
        int len = s.length();
        if(len < 2){
            return s;
        }

        int maxLen=1;
        int begin=0;

        // dp[i][j] 表示 s[i...j]是否是回文串,
        // 重点 dp[i][j] 不是代表 charArray[i]==charArray[j]
        boolean[][] dp = new boolean[len][len];
        for(int i = 0; i< len;i++){
            dp[i][i]=true;
        }
        char[] charArray = s.toCharArray();
        // 注意:左下角先填,下面是先按照列j填,然后按照行i填写
        for(int j = 1; j < len; j++){
            for(int i = 0;i<j;i++){
                if(charArray[i]!=charArray[j]){
                    dp[i][j]=false;
                }else{
                    if(j-i<3){ // 如果其子串[i+1...j-1]含有0或1个元素的时候,则不需要状态转移
                        dp[i][j]=true;
                    }else { // 此时需要进行状态转移,转移到左下角 下一行上一列
                        dp[i][j] = dp[i+1][j-1]; // 利用之前计算得到的结果
                    }
                }

                // 只要 dp[i][j]==true成立,就表示子串 s[i...j]是回文
                // 此时记录回文长度和回文起始位置
                if(dp[i][j] && j-i+1 > maxLen){
                    maxLen = j - i + 1;
                    begin = i;
                }
            }
        }
        return s.substring(begin, begin + maxLen);
    } 

}

4. Manacher算法 

最后是这个问题的终极解法,Manacher算法。

专门用于查找最长回文子串的算法,时间复杂度为O(n)。

面试不要求,有时间时学习下。

力扣链接。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值