[python]DNA序列编辑距离 | 豆包MarsCode AI刷题

51 阅读4分钟

问题描述

小R正在研究DNA序列,他需要一个函数来计算将一个受损DNA序列(dna1)转换成一个未受损序列(dna2)所需的最少编辑步骤。编辑步骤包括:增加一个碱基、删除一个碱基或替换一个碱基。


测试样例

样例1:

输入:dna1 = "AGT",dna2 = "AGCT"
输出:1

样例2:

输入:dna1 = "AACCGGTT",dna2 = "AACCTTGG"
输出:4

样例3:

输入:dna1 = "ACGT",dna2 = "TGC"
输出:3

样例4:

输入:dna1 = "A",dna2 = "T"
输出:1

样例5:

输入:dna1 = "GGGG",dna2 = "TTTT"
输出:4


这是典型的编辑距离问题,可以使用动态规划来求解。

定义一个二维数组dp,其中dp[i][j]表示将dna1的前i个字符转换成dna2的前j个字符所需的最少编辑步骤。

1. DP数组的初始化
  • dp[i][0]:将dna1的前i个字符转换成空字符串,需要i次删除操作,因此dp[i][0] = i
  • dp[0][j]:将空字符串转换成dna2的前j个字符,需要j次插入操作,因此dp[0][j] = j
2. 状态转移方程
  • 如果dna1[i-1] == dna2[j-1],表示当前字符相同,不需要额外操作,直接继承前一状态:dp[i][j] = dp[i-1][j-1]

  • 如果dna1[i-1] != dna2[j-1],则可以进行以下三种操作:

    • 插入:将dna1的前i个字符转换成dna2的前j-1个字符,再插入dna2[j-1],所需步骤为dp[i][j-1] + 1
    • 删除:将dna1的前i-1个字符转换成dna2的前j个字符,再删除dna1[i-1],所需步骤为dp[i-1][j] + 1
    • 替换:将dna1的前i-1个字符转换成dna2的前j-1个字符,再将dna1[i-1]替换为dna2[j-1],所需步骤为dp[i-1][j-1] + 1

取三者的最小值即为dp[i][j]的值:

dp[i][j]=min(dp[i1][j]+1, dp[i][j1]+1, dp[i1][j1]+1)dp[i][j]=min⁡(dp[i−1][j]+1, dp[i][j−1]+1, dp[i−1][j−1]+1)
  • 可视化示例
    以下是对于dna1 = "ABC", dna2 = "AC"dp数组填充过程的可视化示例:

dp[i][j]j = 0j = 1Aj = 2C
i = 001(插入A2(插入AC
i = 1A1(删除A0(相同字符)1(替换BC
i = 2AB2(删除AB1(删除B1(相同字符)
i = 3ABC3(删除ABC2(删除C2(删除B
  • 首先初始化第一行和第一列。第一行表示将空字符串转换为dna2的不同长度前缀所需的操作数,即依次插入字符,所以dp[0][1] = 1(插入A),dp[0][2] = 2(插入AC)。第一列表示将dna1的不同长度前缀转换为空字符串所需的操作数,即依次删除字符,如dp[1][0] = 1(删除A),dp[2][0] = 2(删除AB)等。

  • i = 1j = 1时,dna1[0]A)和dna2[0]A)相同,所以dp[1][1] = 0,无需额外操作。

  • i = 1j = 2时,dna1[0]A)和dna2[1]C)不同。此时有三种选择:

    • 删除:dp[0][2] + 1 = 3(先将空字符串转换为AC,再删除A,但这不是最小值)。
    • 插入:dp[1][1] + 1 = 1(先将A转换为A,再插入C)。
    • 替换:dp[0][1] + 1 = 2(先将空字符串转换为A,再替换AC)。取最小值,所以dp[1][2] = 1(这里实际是替换操作)。
  • 以此类推,完成整个dp数组的填充,最终答案是dp[3][2] = 2

3. 最终结果

最终答案为dp[m][n],其中mn分别是dna1dna2的长度。

完整的Python实现如下:

def solution(dna1, dna2):
    m, n = len(dna1), len(dna2)
    dp = [[0] * (n + 1) for _ in range(m + 1)]
    
    # 初始化
    for i in range(m + 1):
        dp[i][0] = i
    for j in range(n + 1):
        dp[0][j] = j
    
    # 动态规划填表
    for i in range(1, m + 1):
        for j in range(1, n + 1):
            if dna1[i - 1] == dna2[j - 1]:
                dp[i][j] = dp[i - 1][j - 1]  # 字符相同,无需操作
            else:
                dp[i][j] = min(dp[i - 1][j] + 1,     # 删除
                               dp[i][j - 1] + 1,     # 插入
                               dp[i - 1][j - 1] + 1) # 替换
    return dp[m][n]
  • 时间复杂度O(m * n),其中mn分别是dna1dna2的长度。我们需要填充一个m * n大小的DP表。

  • 空间复杂度O(m * n),用于存储DP表。如果仅需要最终答案,可以优化空间复杂度至O(min(m, n))