最长回文子串(动态规划解法)
字数 1271 2025-11-13 02:11:13

最长回文子串(动态规划解法)

我将为您详细讲解最长回文子串问题的动态规划解法。

问题描述
给定一个字符串s,找到s中最长的回文子串。回文串是正着读和反着读都一样的字符串。

示例
输入:s = "babad"
输出:"bab" 或 "aba"

解题思路

1. 定义状态
我们定义dp[i][j]表示字符串s从索引i到j的子串是否是回文串。

  • 如果dp[i][j] = true,表示s[i...j]是回文串
  • 如果dp[i][j] = false,表示s[i...j]不是回文串

2. 状态转移方程
对于任意子串s[i...j],判断其是否为回文串需要满足:

  • 基本情况1:当i = j时,单个字符肯定是回文串
  • 基本情况2:当j = i + 1时,两个相邻字符,只有当s[i] = s[j]时才是回文串
  • 一般情况:当j > i + 1时,s[i...j]是回文串当且仅当:
    • s[i] = s[j](首尾字符相同)
    • 且s[i+1...j-1]是回文串(内部子串是回文串)

用状态转移方程表示:

  • 如果i = j:dp[i][j] = true
  • 如果j = i + 1:dp[i][j] = (s[i] == s[j])
  • 如果j > i + 1:dp[i][j] = (s[i] == s[j]) && dp[i+1][j-1]

3. 初始化
对角线上的元素dp[i][i] = true,因为单个字符都是回文串。

4. 遍历顺序
由于dp[i][j]依赖于dp[i+1][j-1],即左下角的值,所以我们需要:

  • 按子串长度从小到大遍历
  • 先遍历子串长度,再遍历起始位置

5. 记录结果
在填充dp表的过程中,记录找到的最长回文子串的起始位置和长度。

详细实现步骤

让我们以s = "babad"为例:

步骤1:初始化
创建5x5的dp表,所有对角线元素设为true:

   b a b a d
b  T
a    T
b      T
a        T
d          T

步骤2:处理长度为2的子串
检查所有相邻字符对:

  • "ba":b≠a → false
  • "ab":a≠b → false
  • "ba":b≠a → false
  • "ad":a≠d → false

步骤3:处理长度为3的子串

  • "bab":首尾b=b,且中间"a"是回文 → true
  • "aba":首尾a=a,且中间"b"是回文 → true
  • "bad":首尾b≠d → false

此时找到"bab"和"aba",长度3

步骤4:处理长度为4的子串

  • "baba":首尾b≠a → false
  • "abad":首尾a≠d → false

步骤5:处理长度为5的子串

  • "babad":首尾b≠d → false

最终最长回文子串是"bab"或"aba"。

代码实现

def longestPalindrome(s: str) -> str:
    n = len(s)
    if n < 2:
        return s
    
    # 初始化dp表
    dp = [[False] * n for _ in range(n)]
    
    # 所有单个字符都是回文
    for i in range(n):
        dp[i][i] = True
    
    max_len = 1
    start = 0
    
    # 按子串长度遍历
    for length in range(2, n + 1):
        for i in range(n - length + 1):
            j = i + length - 1
            
            if s[i] == s[j]:
                if length == 2:
                    dp[i][j] = True
                else:
                    dp[i][j] = dp[i + 1][j - 1]
            else:
                dp[i][j] = False
            
            # 更新最长回文子串
            if dp[i][j] and length > max_len:
                max_len = length
                start = i
    
    return s[start:start + max_len]

复杂度分析

  • 时间复杂度:O(n²),需要填充n²大小的dp表
  • 空间复杂度:O(n²),需要n²大小的dp表

优化思路
可以通过中心扩展法将空间复杂度优化到O(1),但动态规划解法的思路更加清晰直观,适合理解回文子串的判断逻辑。

最长回文子串(动态规划解法) 我将为您详细讲解最长回文子串问题的动态规划解法。 问题描述 给定一个字符串s,找到s中最长的回文子串。回文串是正着读和反着读都一样的字符串。 示例 输入:s = "babad" 输出:"bab" 或 "aba" 解题思路 1. 定义状态 我们定义dp[ i][ j ]表示字符串s从索引i到j的子串是否是回文串。 如果dp[ i][ j] = true,表示s[ i...j ]是回文串 如果dp[ i][ j] = false,表示s[ i...j ]不是回文串 2. 状态转移方程 对于任意子串s[ i...j ],判断其是否为回文串需要满足: 基本情况1:当i = j时,单个字符肯定是回文串 基本情况2:当j = i + 1时,两个相邻字符,只有当s[ i] = s[ j ]时才是回文串 一般情况:当j > i + 1时,s[ i...j ]是回文串当且仅当: s[ i] = s[ j ](首尾字符相同) 且s[ i+1...j-1 ]是回文串(内部子串是回文串) 用状态转移方程表示: 如果i = j:dp[ i][ j ] = true 如果j = i + 1:dp[ i][ j] = (s[ i] == s[ j ]) 如果j > i + 1:dp[ i][ j] = (s[ i] == s[ j]) && dp[ i+1][ j-1 ] 3. 初始化 对角线上的元素dp[ i][ i ] = true,因为单个字符都是回文串。 4. 遍历顺序 由于dp[ i][ j]依赖于dp[ i+1][ j-1 ],即左下角的值,所以我们需要: 按子串长度从小到大遍历 先遍历子串长度,再遍历起始位置 5. 记录结果 在填充dp表的过程中,记录找到的最长回文子串的起始位置和长度。 详细实现步骤 让我们以s = "babad"为例: 步骤1:初始化 创建5x5的dp表,所有对角线元素设为true: 步骤2:处理长度为2的子串 检查所有相邻字符对: "ba":b≠a → false "ab":a≠b → false "ba":b≠a → false "ad":a≠d → false 步骤3:处理长度为3的子串 "bab":首尾b=b,且中间"a"是回文 → true "aba":首尾a=a,且中间"b"是回文 → true "bad":首尾b≠d → false 此时找到"bab"和"aba",长度3 步骤4:处理长度为4的子串 "baba":首尾b≠a → false "abad":首尾a≠d → false 步骤5:处理长度为5的子串 "babad":首尾b≠d → false 最终最长回文子串是"bab"或"aba"。 代码实现 复杂度分析 时间复杂度:O(n²),需要填充n²大小的dp表 空间复杂度:O(n²),需要n²大小的dp表 优化思路 可以通过中心扩展法将空间复杂度优化到O(1),但动态规划解法的思路更加清晰直观,适合理解回文子串的判断逻辑。