leetcode-最长回文子串

207 阅读3分钟

这是我参与更文挑战的第5天,活动详情查看: 更文挑战

第5天,继续挑战第5题。这题似曾相识,跟最长公共子串非常像,一样是使用动态规划的方法。学生时代刚学算法,觉得动态规划是最难的,后来做的多了,相似的题目也能举一反三,不过其实自己知道还没有深入的理解,遇到题目再慢慢深入吧。

题目

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

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

示例 2:
输入:s = "cbbd"
输出:"bb"

示例 3:
输入:s = "a"
输出:"a"

示例 4:
输入:s = "ac"
输出:"a"

思路

之前也说过,一般遇到算法题,先想想如果不考虑时间复杂度和空间复杂度,怎么样找到结果,理解题目意思后,然后再看看如何优化时间复杂度和空间复杂度。这题暴力的解法,就是遍历每个子串,然后判断每个子串是不是回文。遍历每个子串的时间复杂度是O(n^2),判断每个子串的时间复杂度是O(n),所以整体来说,时间复杂度为O(n^3);空间复杂度的话,不需要存下每个子串,只要存当前子串和最长回文子串,空间复杂度为O(n)。比较下来,时间复杂度是我们这题需要解决的问题。

之前说过,这题起始跟最长公共子串非常像。这里判断子串这一步可以优化,因为可以利用之前判断的结果。我们判断一个子串S(start)~S(end)是否是回文,其实必须满足2个条件:1、S(start)=S(end),2、S(start+1)~S(end-1)是回文,即判断S(start)~S(end)的时候,可以利用之前判断的S(start+1)~S(end-1)的结果。

最长回文子串.png

如果我们从子串长度的从小到大来遍历,把每个子串是否是回文的结果存下来,那么每次判断子串是否是回文,时间复杂度O(1),因为遍历所有子串还是必不可少,整体的时间复杂度就是O(n^2);因为这里要把每个子串是否是回文存下来,所以空间复杂度就是O(n^2),所以这个方法是用增加空间复杂度去换取了减少时间复杂度,这个思想在动态规划的题目中经常使用到。

注意点

上面说的方法,成立的前提条件是子串长度>=2,所以对于长度为1和2的情况,需要单独讨论一下。长度为1时,显然子串可以直接认定为回文;长度为2时,只要满足S(start)=S(end)就可以认为是回文,子串S(start+1)~S(end-1)并不存在

Java版本代码

class Solution {
    public String longestPalindrome(String s) {
        int length = s.length();
        // 代表从start到end的字串是否是回文,因为end>=start,从形状上说,这是一个在右上角的三角形而不是正方形
        boolean[][] dp = new boolean[length][length];
        int maxLen = 0;
        int maxStart = 0;
        // 用长度进行for,计算到较大长度的子串时,长度较短的子串已经被计算过了
        for (int len = 1; len <= length; len++) {
            for (int start = 0; start < length; start++) {
                int end = start + len - 1;
                if (end >= length) {
                    break;
                }
                if (start == end) {
                    // 相等时,必然是回文
                    dp[start][end] = true;
                } else if (end - start == 1) {
                    // 相邻时,是否回文取决于是否相等
                    dp[start][end] = s.charAt(start) == s.charAt(end);
                } else {
                    // 其他情况下,取决于start和end位置上字符是否相等且dp[start+1][end-1]
                    dp[start][end] = (s.charAt(start) == s.charAt(end)) && dp[start+1][end-1];
                }
                if (dp[start][end] && len > maxLen) {
                    maxLen = len;
                    maxStart = start;
                }
            }
        }
        return s.substring(maxStart, maxStart + maxLen);
    }
}