【LeetCode 516 动态规划】最长回文子序列

文章目录

1. 题目

给你一个字符串 s ,找出其中最长的回文子序列,并返回该序列的长度。

子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。

1.1 示例

  • 示例 1 1 1 :

    • 输入: s = "bbbab"
    • 输出: 4 4 4 ;
    • 解释: 一个可能的最长回文子序列为 "bbbb"
  • 示例 2 2 2 :

    • 输入: s = "cbbd"
    • 输出: 2 2 2 ;
    • 解释: 一个可能的最长回文子序列为 "bb"

1.2 说明

1.3 限制

  • 1 <= s.length <= 1000
  • s 仅由小写英文字母组成

2. 解法一(基于最长公共子序列)

2.1 分析

先将字符串翻转,然后求两个字符串的公共子序列

2.2 解答

class Solution:
    def longest_palindrome_subseq(self, s: str) -> int:
        text1 = s
        text2 = s[::-1]
        m, n = len(text1), len(text2)
        dp = [[0] * (n + 1) for _ in range(m + 1)]
        for i in range(1, m + 1):
            for j in range(1, n + 1):
                if text1[i - 1] == text2[j - 1]:
                    dp[i][j] = dp[i - 1][j - 1] + 1
                else:
                    dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
        return dp[m][n]


def main():
    s = "bbbab"
    sln = Solution()
    print(sln.longest_palindrome_subseq(s))  # 4


if __name__ == '__main__':
    main()

2.3 复杂度

  • 时间复杂度: O ( n 2 ) O(n^2) O(n2) ;
  • 空间复杂度: O ( n 2 ) O(n^2) O(n2) 。

3. 解法二(直接动态规划)

3.1 分析

对于一个子序列而言,如果它是回文子序列,并且长度大于 2 2 2 ,那么将它首尾的两个字符去除之后,它仍然是个回文子序列。因此可以用动态规划的方法计算给定字符串的最长回文子序列。

dp[i][j] 表示字符串 s 的下标范围 [i, j] 内的最长回文子序列的长度。假设字符串 s 的长度为 n n n ,则只有当 0 ≤ i ≤ j < n 时,才会有 dp[i][j] > 0,否则 dp[i][j] = 0

由于任何长度为 1 1 1 的子序列都是回文子序列,因此动态规划的边界情况是,对任意 0 ≤ i = j < n,都有 dp[i][i] = 1

0 ≤ i < j < n 时,计算 dp[i][j] 需要分别考虑 s[i]s[j] 相等和不相等的情况:

  • 如果 s[i] == s[j],则首先得到 s 的下标范围 [i + 1, j − 1] 内的最长回文子序列,然后在该子序列的首尾分别添加 s[i]s[j] ,即可得到 s 的下标范围 [i, j] 内的最长回文子序列,因此 dp[i][j] = dp[i + 1][j − 1] + 2

  • 如果 s[i] != s[j],则 s[i]s[j] 不可能同时作为同一个回文子序列的首尾,因此 dp[i][j] = max(dp[i + 1][j], dp[i][j − 1])

由于状态转移方程都是从长度较短的子序列向长度较长的子序列转移,因此需要注意动态规划的循环顺序。

【LeetCode 516 动态规划】最长回文子序列

具体地,如上图所示, dp[i][j] 的状态转移依赖于其左、下以及左下角的状态;因此,如下图所示,二维 dp 表格的填写应该按照先下后上,先左后右的顺序进行填写。

【LeetCode 516 动态规划】最长回文子序列

最终得到 dp[0][n−1] 即为字符串 s 的最长回文子序列的长度。

3.2 解答

class Solution:
    def longest_palindrome_subseq(self, s: str) -> int:
        n = len(s)
        dp = [[0] * n for _ in range(n)]
        for i in range(n - 1, -1, -1):
            dp[i][i] = 1
            for j in range(i + 1, n):
                if s[i] == s[j]:
                    dp[i][j] = dp[i + 1][j - 1] + 2
                else:
                    dp[i][j] = max(dp[i + 1][j], dp[i][j - 1])
        return dp[0][n - 1]

3.3 复杂度

  • 时间复杂度: O ( n 2 ) O(n^2) O(n2) ,其中 n n n 是字符串 s 的长度。动态规划需要计算的状态数是 O ( n 2 ) O(n^2) O(n2);
  • 空间复杂度: O ( n 2 ) O(n^2) O(n2) ,其中 n n n 是字符串 s 的长度。需要创建二维数组 dp,空间是 O ( n 2 ) O(n^2) O(n2) 。
上一篇:Python的cmp_to_key()函数详解【部分翻译+leetcode题目分析】


下一篇:exlipse初始化配置总结