数据结构与算法之 leetcode 动态规划最长公共子序列 LCS 问题

数据结构与算法阿木 发布于 6 天前 2 次阅读


动态规划解决 LeetCode LCS 问题:最长公共子序列

动态规划(Dynamic Programming,简称 DP)是一种在数学、管理科学、计算机科学、经济学和生物信息学等领域中广泛应用的方法。它通过将复杂问题分解为更小的子问题,并存储这些子问题的解,从而避免重复计算,提高算法效率。在 LeetCode 中,动态规划问题非常常见,其中最长公共子序列(Longest Common Subsequence,简称 LCS)问题是一个典型的例子。

LCS 问题是指给定两个序列,找出它们的最长公共子序列。这个问题在生物信息学、文本编辑、语音识别等领域有着广泛的应用。本文将围绕 LCS 问题,使用动态规划方法进行求解,并探讨其实现细节。

LCS 问题分析

假设有两个序列 A 和 B,长度分别为 m 和 n。我们可以定义一个二维数组 dp[m+1][n+1],其中 dp[i][j] 表示 A 的前 i 个字符和 B 的前 j 个字符的最长公共子序列的长度。

状态转移方程

为了求解 dp[i][j],我们需要考虑以下几种情况:

1. 如果 A[i-1] 和 B[j-1] 相同,那么 dp[i][j] = dp[i-1][j-1] + 1。

2. 如果 A[i-1] 和 B[j-1] 不同,那么 dp[i][j] = max(dp[i-1][j], dp[i][j-1])。

边界条件

1. 当 i = 0 或 j = 0 时,dp[i][j] = 0,因为空序列与任何序列的最长公共子序列长度都是 0。

动态规划实现

下面是使用 Python 实现的 LCS 问题动态规划解法:

python

def longest_common_subsequence(A, B):


m, n = len(A), len(B)


dp = [[0] (n + 1) for _ in range(m + 1)]

for i in range(1, m + 1):


for j in range(1, n + 1):


if A[i - 1] == B[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]

测试


A = "ABCBDAB"


B = "BDCAB"


print(longest_common_subsequence(A, B)) 输出:4


优化空间复杂度

上述实现中,我们使用了二维数组 dp 来存储中间结果。由于状态转移方程只依赖于上一行的数据,因此我们可以将空间复杂度从 O(mn) 降低到 O(min(m, n))。

python

def longest_common_subsequence_optimized(A, B):


m, n = len(A), len(B)


if m < n:


A, B = B, A


m, n = n, m

dp = [0] (n + 1)


for i in range(1, m + 1):


prev, curr = 0, 0


for j in range(1, n + 1):


prev, curr = curr, max(prev + (A[i - 1] == B[j - 1]), curr)


dp = [prev] + dp

return dp[n]

测试


A = "ABCBDAB"


B = "BDCAB"


print(longest_common_subsequence_optimized(A, B)) 输出:4


总结

本文介绍了 LCS 问题及其动态规划解法。通过分析状态转移方程和边界条件,我们实现了 LCS 问题的动态规划解法,并对其空间复杂度进行了优化。在实际应用中,动态规划方法可以帮助我们解决许多复杂问题,提高算法效率。

扩展阅读

1. 《算法导论》

2. 《动态规划:原理与实例》

3. LeetCode LCS 问题相关题目

希望本文能帮助您更好地理解 LCS 问题及其动态规划解法。如果您有任何疑问或建议,请随时提出。