Loading

Medium | LeetCode 5. 最长回文子串 | 动态规划 | 中心拓展法

5. 最长回文子串

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

示例 1:

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

示例 2:

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

示例 3:

输入:s = "a"
输出:"a"

示例 4:

输入:s = "ac"
输出:"a"

提示:

  • 1 <= s.length <= 1000
  • s 仅由数字和英文字母(大写和/或小写)组成

解题思路

方法一: 动态规划

P(i,j) 表示字符串 s 的第 i 到 j 个字母组成的串(下文表示成 s[i:j])是否为回文串, 动态规划方程如下:

\[P(i, j)=P(i+1, j-1) \wedge\left(S_{i}==S_{j}\right) \]

在状态转移方程中,我们是从长度较短的字符串向长度较长的字符串进行转移的,因此一定要注意动态规划的循环顺序。从边界为0出发, 逐渐向外拓展, 先计算长度为1的子串是否是回文串, 然后计算长度为2的所有子串是否是回文串, 再次基础上再计算长度为3的是否是回文串, 然后逐步计算到长度为N的是否是回文串。

这种动态规划的算法实际上就是填写一张二维表格, 所以时间复杂度是O(N^2), 空间复杂度是O(N^2).

public String longestPalindrome(String s) {
    int n = s.length();
    // dp[i][j] 表示 从i到j是一个回文串
    boolean[][] dp = new boolean[n][n];
    String ans = "";
    for (int len = 0; len < n; ++len) {
        for (int i = 0; i + len < n; ++i) {
            int j = i + len;
            if (len == 0) {
                // 初始化对角线
                dp[i][j] = true;
            } else if (len == 1) {
                // 只有两个元素的情况
                dp[i][j] = (s.charAt(i) == s.charAt(j));
            } else {
                // 三个及以上元素的情况
                dp[i][j] = (s.charAt(i) == s.charAt(j) && dp[i + 1][j - 1]);
            }
            // 每次找到一个更大的长度的回文子串, 变更新回文子串。
            if (dp[i][j] && len + 1 > ans.length()) {
                ans = s.substring(i, i + len + 1);
            }
        }
    }
    return ans;
}

方法二: 中心拓展法

中心拓展法的思想是, 枚举所有中心。 从中心位置处开始向左右两边延伸, 并且在延伸过程当中分为奇数和偶数两种情况讨论。在延伸直到不可拓展的最大值, 即可得到最大的回文子串。

这种算法只需要枚举中心并且原地向两端延伸, 所以这种算法的时间复杂度是O(N^2), 不需要其他的任何的额外的存储空间, 所以空间复杂度是O(1)

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);
        // 得到最大的回文子串的长度
        int len = Math.max(len1, len2);
        if (len > end - start) {
            start = i - (len - 1) / 2;
            end = i + len / 2;
        }
    }
    return s.substring(start, end + 1);
}

// 中心拓展法计算回文子串的长度
public 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;
}
posted @ 2021-01-30 23:52  反身而诚、  阅读(103)  评论(0编辑  收藏  举报