📜  DAA-最长公共子序列(1)

📅  最后修改于: 2023-12-03 15:14:35.549000             🧑  作者: Mango

DAA-最长公共子序列

什么是最长公共子序列(LCS)

最长公共子序列(Longest Common Subsequence, 简称LCS)指在给定的两个序列中找到一个最长的公共子序列,子序列是指在原序列中不要求连续,但在各自内部元素的先后顺序必须保持一致。

举个例子

比如说我们有两个字符串 "ABCBDAB""BDCABA",它们的一个最长公共子序列就是 "BCBA"。但是这个最长公共子序列并不唯一,还可以选择 "BDAB""BCAB""BCDA" 等等。

究竟有没有算法可以找到最长公共子序列呢?

答案是有的。动态规划算法可以找到两个字符串的最长公共子序列。这里主要介绍 LCA 的动态规划实现。

动态规划实现

动态规划是一种自底向上的算法。我们用一个二维数组来保存两个字符串中任意长度的子序列的最长公共子序列的长度(下表加粗的数字是行和列的标号):

| | |B |D |C |A |B |A | |:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:| | | |0 |0 |0 |0 |0 |0 | |A |0 |0 |0 |0 |1 |0 |1| |B |0 |1 |0 |0 |0 |1 |0 | |C |0 |0 |0 |1 |0 |0 |0 | |B |0 |1 |0 |0 |0 |1 |0 | |D |0 |0 |1 |0 |0 |0 |0 | |A |0 |0 |0 |0 |1 |0 |1|

首先确定初始状态:两个串中的任意单个字符的 LCS 长度都是 0。上表中的第一行和第一列都是0。

然后,我们从表格的第二行、第二列开始填表。如果当前的字符相同,我们可以将它们都添加到当前 LCS 的末尾,LCS 的长度加1,而下一步就是求两个不含这两个字符的字符串的 LCS。具体来说,我们既可以向左移动,也可以向上移动。如果当前的字符不同,那么我们需要继续向左或向上,以前面得到的最长 LCS 为准。

我们可以使用以下公式来填充其余的表格:

if a[i] == b[j], table(i, j) = table(i - 1, j - 1) + 1;
else table(i, j) = max(table(i - 1, j), table(i, j - 1));

我们可以逐行递增地填充表格。填充表格的最后一个元素就是最长公共子序列的长度。同样,我们可以使用类似栈的方法来找出 LCS 本身。

下面是这个算法的实现。

def LCS(a, b):
    m, n = len(a), len(b)
    table = [[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]:
                table[i][j] = table[i - 1][j - 1] + 1
            else:
                table[i][j] = max(table[i - 1][j], table[i][j - 1])

    return table[m][n]
总结

动态规划算法是一种非常优秀的算法。它可以处理一些比较复杂的问题,例如 LCS。它是一个高效的算法,时间复杂度为 $𝑂(𝑛^2)$,空间复杂度也是 $𝑂(𝑛^2)$。