使用 Python 解题 - DNA序列编辑距离

89 阅读3分钟

一,题目详情

1,问题描述

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

2,测试样例

样例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

c2a4008db989911f5e3de39fd0cbeb5.png

二,解题思路

1,问题分析

这是一个经典的编辑距离问题,需要找到将一个字符串转换为另一个字符串的最少操作次数。操作包括插入、删除和替换。

2,算法策略

使用动态规划来解决这个问题。动态规划表 dp[i][j] 表示将 dna1 的前 i 个字符转换为 dna2 的前 j 个字符所需的最少操作次数。

具体步骤如下:

  1. 初始化一个 (m+1) x (n+1) 的动态规划表,其中 m 是 dna1 的长度,n 是 dna2 的长度。
  2. 填充边界条件:dp[i][0] = i(删除 dna1 的前 i 个字符),dp[0][j] = j(插入 dna2 的前 j 个字符)。
  3. 对于每个 dp[i][j],如果 dna1[i-1] == dna2[j-1],则 dp[i][j] = dp[i-1][j-1];否则,取替换、删除、插入三种操作的最小值加1。

3,逐步推演(以样例1为例)

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

动态规划表的填充过程如下:

i/j01234
001234
110123
221012
332112

最终结果为 dp[3][4] = 1。

三,代码实现

def solution(dna1, dna2):
    m = len(dna1)
    n = 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  # 插入所有字符
    
    # 填充dp表
    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:
                replace = dp[i-1][j-1] + 1
                delete = dp[i-1][j] + 1
                insert = dp[i][j-1] + 1
                dp[i][j] = min(replace, delete, insert)
    
    return dp[m][n]

1,复杂度分析

  • 时间复杂度:O(m*n)

    • 遍历整个动态规划表
  • 空间复杂度:O(m*n)

    • 存储动态规划表

2,边界测试

if __name__ == "__main__":
    # 常规测试
    print(solution("AGT", "AGCT") == 1)          # 样例1
    print(solution("AACCGGTT", "AACCTTGG") == 4) # 样例2
    print(solution("ACGT", "TGC") == 3)          # 样例3
    print(solution("A", "T") == 1)               # 样例4
    print(solution("GGGG", "TTTT") == 4)         # 样例5
    print(solution("AGCTTAGC", "AGCTAGCT") == 2 )
    print(solution("AGCCGAGC", "GCTAGCT") == 4)

四,总结

通过动态规划,我们实现了:

  1. 最少编辑步骤:找到将一个DNA序列转换为另一个序列的最少操作次数
  2. 清晰的逻辑:利用动态规划表逐步推导
  3. 普适性:适用于所有DNA序列

这种解法不仅高效,还易于理解和实现。当遇到“需要计算两个序列之间的转换成本”类问题时,动态规划往往是解决问题的关键策略。

8890b1487ce40b7e01e5b0c0f63c791.png