区间动态规划例题:最长回文子序列的长度问题(状态压缩优化版本)
字数 1404 2025-11-13 01:39:34

区间动态规划例题:最长回文子序列的长度问题(状态压缩优化版本)

题目描述

给定一个字符串s,找到其中最长的回文子序列的长度。子序列定义为:通过删除原字符串中的一些字符(也可以不删除)而不改变剩余字符的相对顺序得到的新字符串。

例如:

  • 输入:"bbbab"
  • 输出:4
  • 解释:最长回文子序列是"bbbb"

解题过程

1. 问题分析
回文子序列不要求字符在原字符串中连续,但必须保持相对顺序。我们需要找到最长的这样的序列。

2. 基础DP思路
定义dp[i][j]表示子串s[i...j]的最长回文子序列长度。

状态转移方程:

  • 如果s[i] == s[j]:
    • 当i == j时,dp[i][j] = 1(单个字符)
    • 当i + 1 == j时,dp[i][j] = 2(两个相同字符)
    • 否则,dp[i][j] = dp[i+1][j-1] + 2
  • 如果s[i] != s[j]:
    • dp[i][j] = max(dp[i+1][j], dp[i][j-1])

3. 状态压缩优化
观察发现,计算dp[i][j]时只依赖于:

  • 当前行的dp[i+1][j]
  • 前一行的dp[i][j-1]
  • 前一行的dp[i+1][j-1]

因此可以用一维数组优化空间复杂度。

4. 优化后的DP实现

def longestPalindromeSubseq(s: str) -> int:
    n = len(s)
    # 使用一维数组,dp[j]表示当前处理行中区间[i,j]的最长回文子序列长度
    dp = [0] * n
    
    for i in range(n-1, -1, -1):
        # pre保存dp[i+1][j-1]的值
        pre = 0
        dp[i] = 1  # 单个字符的情况
        
        for j in range(i+1, n):
            # temp保存当前dp[j]的值,用于下一轮作为pre
            temp = dp[j]
            
            if s[i] == s[j]:
                # dp[i][j] = dp[i+1][j-1] + 2
                dp[j] = pre + 2
            else:
                # dp[i][j] = max(dp[i+1][j], dp[i][j-1])
                dp[j] = max(dp[j], dp[j-1])
            
            # 更新pre为当前轮的dp[i+1][j-1]
            pre = temp
    
    return dp[n-1]

5. 逐步计算示例(s = "bbbab")

初始状态:dp = [0,0,0,0,0]

i=4(从右往左处理):

  • dp[4] = 1
  • 最终dp = [0,0,0,0,1]

i=3:

  • dp[3] = 1
  • j=4: s[3]=='a' != s[4]=='b',dp[4]=max(1,0)=1
  • dp = [0,0,0,1,1]

i=2:

  • dp[2] = 1
  • j=3: s[2]=='b' != s[3]=='a',dp[3]=max(1,1)=1
  • j=4: s[2]=='b' == s[4]=='b',dp[4]=pre+2=0+2=2
  • dp = [0,0,1,1,2]

i=1:

  • dp[1] = 1
  • j=2: s[1]=='b' == s[2]=='b',dp[2]=pre+2=0+2=2
  • j=3: s[1]=='b' != s[3]=='a',dp[3]=max(1,2)=2
  • j=4: s[1]=='b' == s[4]=='b',dp[4]=pre+2=1+2=3
  • dp = [0,1,2,2,3]

i=0:

  • dp[0] = 1
  • j=1: s[0]=='b' == s[1]=='b',dp[1]=pre+2=0+2=2
  • j=2: s[0]=='b' == s[2]=='b',dp[2]=pre+2=1+2=3
  • j=3: s[0]=='b' != s[3]=='a',dp[3]=max(2,3)=3
  • j=4: s[0]=='b' == s[4]=='b',dp[4]=pre+2=2+2=4
  • 最终dp = [1,2,3,3,4]

结果为dp[4] = 4

6. 复杂度分析

  • 时间复杂度:O(n²)
  • 空间复杂度:O(n)(从O(n²)优化到O(n))

这种状态压缩方法在保持正确性的同时,显著降低了空间复杂度。

区间动态规划例题:最长回文子序列的长度问题(状态压缩优化版本) 题目描述 给定一个字符串s,找到其中最长的回文子序列的长度。子序列定义为:通过删除原字符串中的一些字符(也可以不删除)而不改变剩余字符的相对顺序得到的新字符串。 例如: 输入:"bbbab" 输出:4 解释:最长回文子序列是"bbbb" 解题过程 1. 问题分析 回文子序列不要求字符在原字符串中连续,但必须保持相对顺序。我们需要找到最长的这样的序列。 2. 基础DP思路 定义dp[ i][ j]表示子串s[ i...j ]的最长回文子序列长度。 状态转移方程: 如果s[ i] == s[ j ]: 当i == j时,dp[ i][ j ] = 1(单个字符) 当i + 1 == j时,dp[ i][ j ] = 2(两个相同字符) 否则,dp[ i][ j] = dp[ i+1][ j-1 ] + 2 如果s[ i] != s[ j ]: dp[ i][ j] = max(dp[ i+1][ j], dp[ i][ j-1 ]) 3. 状态压缩优化 观察发现,计算dp[ i][ j ]时只依赖于: 当前行的dp[ i+1][ j ] 前一行的dp[ i][ j-1 ] 前一行的dp[ i+1][ j-1 ] 因此可以用一维数组优化空间复杂度。 4. 优化后的DP实现 5. 逐步计算示例(s = "bbbab") 初始状态:dp = [ 0,0,0,0,0 ] i=4(从右往左处理): dp[ 4 ] = 1 最终dp = [ 0,0,0,0,1 ] i=3: dp[ 3 ] = 1 j=4: s[ 3]=='a' != s[ 4]=='b',dp[ 4 ]=max(1,0)=1 dp = [ 0,0,0,1,1 ] i=2: dp[ 2 ] = 1 j=3: s[ 2]=='b' != s[ 3]=='a',dp[ 3 ]=max(1,1)=1 j=4: s[ 2]=='b' == s[ 4]=='b',dp[ 4 ]=pre+2=0+2=2 dp = [ 0,0,1,1,2 ] i=1: dp[ 1 ] = 1 j=2: s[ 1]=='b' == s[ 2]=='b',dp[ 2 ]=pre+2=0+2=2 j=3: s[ 1]=='b' != s[ 3]=='a',dp[ 3 ]=max(1,2)=2 j=4: s[ 1]=='b' == s[ 4]=='b',dp[ 4 ]=pre+2=1+2=3 dp = [ 0,1,2,2,3 ] i=0: dp[ 0 ] = 1 j=1: s[ 0]=='b' == s[ 1]=='b',dp[ 1 ]=pre+2=0+2=2 j=2: s[ 0]=='b' == s[ 2]=='b',dp[ 2 ]=pre+2=1+2=3 j=3: s[ 0]=='b' != s[ 3]=='a',dp[ 3 ]=max(2,3)=3 j=4: s[ 0]=='b' == s[ 4]=='b',dp[ 4 ]=pre+2=2+2=4 最终dp = [ 1,2,3,3,4 ] 结果为dp[ 4 ] = 4 6. 复杂度分析 时间复杂度:O(n²) 空间复杂度:O(n)(从O(n²)优化到O(n)) 这种状态压缩方法在保持正确性的同时,显著降低了空间复杂度。