题目

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

  • 子串(substring):原始字符串的一个连续子集;
  • 子序列(subsequence):原始字符串的一个子集。

示例1

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

示例2

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

解题思路

动态规划需要有状态和状态转移方程:

  • 状态: d p [ i ] [ j ] dp[i][j] dp[i][j] 表示子串 s [ i ⋯ j ] s[i\cdots j] s[ij] 是否为回文子串

  • 状态转移方程:
    d p [ i ] [ j ] = ( s [ i ] = = s [ j ] ) ∧ ( d p [ i + 1 ] [ j − 1 ] ) dp[i][j] = (s[i]==s[j]) \land (dp[i+1][j-1]) dp[i][j]=(s[i]==s[j])(dp[i+1][j1])

  • 边界条件: i + 1 i+1 i+1 j − 1 j-1 j1 不构成区间,即 ( j − 1 ) − ( i + 1 ) < 1 (j-1)-(i+1)<1 (j1)(i+1)<1,整理得 j − i + 1 < 4 j-i + 1<4 ji+1<4

    意义:表明当 s [ i ⋯ j ] s[i\cdots j] s[ij] 的长度为2或3时,不用检查子串是否回文,因为其本身已经是最基本的子串

  • 初始化: d p [ i ] [ j ] = t r u e dp[i][j]=true dp[i][j]=true(可省略,因为判断时没不到)

  • 输出:在得到一个状态的值为 true 的时候,记录起始位置和长度,填表完成以后再截取

详解

有以下字符串:

字符 b a b a a
下标 0 1 2 3 4

由于 d p [ i ] [ j ] dp[i][j] dp[i][j]参考了 d p [ i + 1 ] [ j − 1 ] dp[i+1][j-1] dp[i+1][j1]的值,即表格中左下方的值,故

  1. 先升序填列
  2. 再升序填行
左边界i\右边界j 0 1 2 3 4
0 TRUE FALSE TRUE FALSE FALSE
1 TRUE FALSE TRUE FALSE
2 TRUE FALSE FALSE
3 TRUE TRUE
4 TRUE

代码

class Solution {
    public String longestPalindrome(String s) {
        int len = s.length();
        if(len == 1) {
            return s;
        }
        int maxLen = 1;
        int start = 0;
        boolean[][] dp = new boolean[len][len];
        for(int j = 1; j < len; j++) {
            for(int i = 0; i < j; i++) {
                if(s.charAt(i) != s.charAt(j)) { // 两边字符不同
                    dp[i][j] = false; // 必不是回文子串
                } else { // 两边字符相同
                    if(j - i < 3){ // 子串长度为2或3
                        dp[i][j] = true; // 必为回文子串
                    } else { // 子串长度大于3
                        dp[i][j] = dp[i+1][j-1]; // 与其之前的子串相关
                    }
                }
                if(dp[i][j] && (j-i+1 > maxLen)) {
                    maxLen = j - i + 1;
                    start = i;
                }
            }
        }
        return s.substring(start, start + maxLen);
    }
}