目录
1.理解问题,给出问题的描述。
给定两个序列 X 和 Y,当另一个序列 Z 既是 X 的子序列又是 Y 的子序列时, Z 是 X 和 Y 的公共子序列。
2.算法设计,包括算法策略与数据结构的选择。
(1)算法策略:
①初始化一个二维数组 dp,其中 dp[i][j]表示 s1 的前 i 个字符和 s2 的前 j 个字符的最长公共子序列的长度。
②遍历两个字符串的所有字符,如果 s1[i-1] == s2[j-1],则 dp[i][j] =dp[i-1][j-1] + 1;否则,dp[i][j] = max(dp[i-1][j], dp[i][j-1])。
③返回 dp[m][n],即 s1 和 s2 的最长公共子序列的长度。
(2)数据结构的选择: 使用一个二维数组 dp 来存储中间结果,其中 dp[i][j]表示 s1 的前 i 个字符和 s2 的前 j 个字符的最长公共子序列的长度。
想要了解动态规划的宝宝可以康康我的这篇文章:
动态规划的基本思想https://blog.csdn.net/a1b2c3666666/article/details/138444468?spm=1001.2014.3001.5502里面详细介绍了动态规划法~
3.描述算法(伪代码)。
function LCS(s1, s2):
m = length(s1)
n = length(s2)
dp = [[0] * (n + 1) for _ in range(m + 1)]
for i in range(1, m + 1):
for j in range(1, n + 1):
if s1[i - 1] == s2[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]
4.算法的正确性证明。
(1)初始化:
当 i=0 或 j=0 时,dp[i][j]=0,因为空序列与任何序列的 最长公共子序列长度为 0。
(2)递推关系:
对于任意 i>0 和 j>0,如果 s1[i-1] == s2[j-1],那么 dp[i][j] = dp[i-1][j-1] + 1;否则,dp[i][j] = max(dp[i-1][j], dp[i][j-1])。 这个递推关系保证了在每一步都能找到当前位置的最长公共子序列长 度。
(3)最优解:
最后返回的 dp[m][n]就是 s1 和 s2 的最长公共子序列的 长度。
5. 算法复杂性分析。
(1)时间复杂性:由于算法需要遍历两个字符串的所有字符,所以时间复杂 性为 O(m*n),其中 m 和 n 分别为两个字符串的长度。
(2)空间复杂性:算法使用了一个二维数组 dp 来存储中间结果,其大小为 (m+1)(n+1),所以空间复杂性为 O(mn)。
6.算法的实现与测试。
代码及运行结果截图:
def longest_common_subsequence(s1, s2):
m, n = len(s1), len(s2)
dp = [[0] * (n + 1) for _ in range(m + 1)]
for i in range(1, m + 1):
for j in range(1, n + 1):
if s1[i - 1] == s2[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]
s1 = "ABCBDAB"
s2 = "BDCAB"
print(longest_common_subsequence(s1, s2)) # 输出:4
7.总结。
1.理解子问题的构建:
动态规划的核心在于将大问题分解为小的、重叠的 子问题,并且确保每个子问题只被计算一次。在 LCS 问题中,通过构建二维 数组 dp[i][j]来记录子问题的解,这有助于我们避免重复计算,并且能够逐 步构建出最终的解。
2.递推关系的直观性:
动态规划的递推关系通常很直观。对于 LCS,如果当 前字符匹配,则当前子问题的解是左上方子问题的解加一;如果不匹配,则 当前子问题的解是上方或左方子问题解的较大者。这种直观的递推关系使得算法容易理解和实现。
3.空间优化的重要性:
在实验中会发现,原始的动态规划解法需要 O(mn) 的空间复杂度,这在两个字符串很长时可能会造成内存使用上的问题。因此,寻找空间优化的方法(例如使用滚动数组技巧将空间复杂度降低到 O(min(m, n))) 是一个重要的学习点。
4.算法效率的权衡:
动态规划虽然能够给出问题的精确解,但在时间复杂度上通常是较高的。对于 LCS 问题,时间复杂度是 O(mn),这意味着对于非常大的字符串,算法可能会运行得很慢。这要求我们在实际应用中考虑权衡 解的精确度和算法的运行时间。
5.正确性证明的必要性:
实现动态规划算法后,理解并证明其正确性是非 常重要的。这不仅涉及到数学归纳法等证明技巧,也加深了对问题和解决方案的理解。
6.实际应用的观察:
在实验中可以观察到动态规划解决 LCS 问题的实际应 用,比如在生物信息学中的基因序列比对,或者在文本编辑器中的比较不同文档的差异等。
7.进一步优化的思考:
实验可能激发对如何进一步优化算法的思考,例如使用分治法结合动态规划来解决 LCS 问题,或者探索其他启发式方法来加快求解速度。
可以给我一个免费的赞赞嘛~拜托啦ovo蟹蟹~
我的算法专栏会持续更新哒~