LeetCode 精选 TOP 面试题(Java 实现)—— 最长回文子串

一、题目描述

1.1 题目
  • 最长回文子串

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

  • 示例 1:

输入: "babad"
输出: "bab"
注意: "aba" 也是一个有效答案。
  • 示例 2:
输入: "cbbd"
输出: "bb"
1.2 知识点
  • 动态规划
1.3 题目链接

二、解题思路

2.1 自研思路

  这道题的解题思路还是比较常规的,我见到这道题的第一想法是直接使用 中心扩散算法 ,即从直接从字符串的开头开始依次遍历每个字符,然后判断它左右的字符 S[i-1] 和 S[i+1] 是否相等(奇数长度回文子串),如果左右字符相等则继续向两侧从扩散,继续比较 S[i-2] 和 S[i+2] 是否相等,一直持续到两个字符不相等为止,队友偶数长度的回文子串判断的方式类似,即 S[i] 是否和 S[i-1] 或 S[i+1] 相等,如果相等则继续向两侧扩散。

  但是因为之前使用过这样的方法,所以这次就换了一个思路,即使用 动态规划 的思路来解决这道题,因为每个回文子串都可看做其子回文子串左右添加相同字符后组成的新回文子串,递归式如下:
在这里插入图片描述
  因为递推式的推导比较简单,所以这里就不再赘述,LeetCode 也提供了比较详细的分析,这里要说的是 LeetCode 给出的解法和评论中给出的解法一般都是使用一个二维的 dp 数组来记录每个子串的状态(是否是回文串),因此算法整体的时间复杂度和空间复杂度都是 O(n^2) 。

  但是通过观察后可以发现其实这道题可以只使用两个一维数组就可以实现动态规划的解法,即一个一个数组用来保存偶数长度的回文子串状态,另一个数据用来保存奇数长度的回文子串状态。这里之所以要使用两个数组的原因主要是因为每个子串的状态都是由其左下角的子串状态来确定的,因此当我们把整个 dp 二维数组压缩为一个一维数组时,可能会造成前一行的记录更新时会覆盖掉后一行需要使用的子串状态,因此为了避免后面后序数组需要的前前序状态被前序数组覆盖掉,所以这里使用了两个数组来交叉保存数据,分别对应奇数长度和偶数长度的子串状态。

2.2 总结

  但其实用动态规划算法来解这道题的效果并不明显,主要的原因就是动态规划算法不管在什么情况下都会整体遍历,即时间复杂度固定 O(n^2),而相反的中心扩散等算法,其实时间复杂度能达到 O(n ^2) 的概率相比于动态规划算法就会低很多,比如对于像 abcdefehsi 这样的字符串,真正需要进行扩散遍历的只有 efe 这个子串,而其他的字符串都因为遍历的时候两遍或相邻字符不相等而直接被过滤掉了,所以这时的时间复杂度甚至可以近似 O(n),相比之下效率就会比动态规划算法高很多了,所以在实例代码中我也提供了 LeetCode 中的中心扩散算法的相关实现,大家可以参考一下。

三、实现代码

3.1 自研实现
// 用时 39ms 超 53% ; 内存超 90%
class Solution {
    public String longestPalindrome(String s) {
        
        int len = s.length();
        if (len < 2){
            return s;
        }

        int start = 0, end = 0, strLen = 0;
        boolean[] dp;
        boolean[] dp1 = new boolean[len]; // 长度为偶数的回文子串记录
        boolean[] dp2 = new boolean[len]; // 长度为奇数的回文子串记录

		// 初始化两个 dp 数组
        for(int i = 0; i < len; i++){
            dp1[i] = true;
            dp2[i] = true;
        }
        
        for(int i = 1; i < len; i++){
            dp = i%2 == 1 ? dp1 : dp2;
            for(int j = 0; j < len-i; j++){
            	// Si == Sj && dp[i+1][j-1] == true
                dp[j] = (s.charAt(j) == s.charAt(j+i)) && dp[j+1];
                // 判断是否需要更替当前最长回文子串
                if(dp[j] && i+1 > strLen){
                    start = j;
                    end = j+i;
                    strLen = i+1;
                }
            }
        }
        return s.substring(start, end+1);
    }
}
3.2 示例代码
// 时间:8ms
class Solution {
    public String longestPalindrome(String s) {
        if (s == null || s.length() < 1) return "";
        int start=0, end=0;
        for(int i=0;i< s.length(); ++i) {
            int len1 = expandAroundCenter(s, i, i);
            int len2 = expandAroundCenter(s, i, i+1);
            len1 = Math.max(len1, len2);
            if(len1 > end-start+1) {
                start=i-(len1-1)/2;
                end=i+len1/2 ;
            }
        }
        return s.substring(start, end+1);
    }

    public int expandAroundCenter(String s, int l, int r) {
        while(l >=0 && r<s.length() && s.charAt(l)==s.charAt(r)) {
            --l;
            ++r;
        }
        return r-l-1;
    }
}
发布了244 篇原创文章 · 获赞 32 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/qq_40697071/article/details/103911288