DNA序列编辑问题 | 豆包MarsCode AI刷题

40 阅读3分钟

问题描述

小R正在研究DNA序列,他需要一个函数来计算将一个受损DNA序列(dna1)转换成一个未受损序列(dna2)所需的最少编辑步骤。编辑步骤包括:增加一个碱基、删除一个碱基或替换一个碱基。 这个问题是经典的 编辑距离问题,要求计算将一个字符串 dna1 转换成另一个字符串 dna2 所需的最少编辑步骤。 每一步操作可以是:

  • 插入:在 dna1 中插入一个字符,使其与 dna2 匹配。
  • 删除:从 dna1 中删除一个字符,使其与 dna2 匹配。
  • 替换:替换 dna1 中的一个字符,使其与 dna2 匹配。

问题分析

这个问题可以通过动态规划来解决。具体方法是构建一个二维的DP表,其中 dp[i][j] 表示将 dna1 的前 i 个字符转换成 dna2 的前 j 个字符所需要的最小编辑距离。

  1. 定义状态

    • dp[i][j] 表示将 dna1[0..i-1] 转换成 dna2[0..j-1] 的最小编辑距离。
  2. 状态转移

    如果 dna1[i-1] == dna2[j-1],那么 dp[i][j] = dp[i-1][j-1](即不需要操作,因为字符相同)。如果 dna1[i-1] != dna2[j-1],则:

    (1) 插入:将 dna1[0..i-1] 转换成 dna2[0..j-2],然后插入一个字符,dp[i][j] = dp[i][j-1] + 1

    (2) 删除:将 dna1[0..i-2] 转换成 dna2[0..j-1],然后删除一个字符,dp[i][j] = dp[i-1][j] + 1

    (3) 替换:将 dna1[0..i-2] 转换成 dna2[0..j-2],然后替换 dna1[i-1] 为 dna2[j-1]dp[i][j] = dp[i-1][j-1] + 1

  3. 初始化

    • dp[0][0] = 0:两个空字符串的编辑距离是0。
    • dp[i][0] = i:将 dna1[0..i-1] 转换为一个空字符串需要 i 次删除操作。
    • dp[0][j] = j:将一个空字符串转换为 dna2[0..j-1] 需要 j 次插入操作。
  4. 目标

    • 当达到 dp[len(dna1)][len(dna2)]时完成。

代码解释

  1. 初始化

    dp[i][0] = i 表示将 dna1 的前 i 个字符转换为一个空字符串需要 i 次删除操作。 dp[0][j] = j 表示将一个空字符串转换为 dna2 的前 j 个字符需要 j 次插入操作。

  2. 填充 DP 表

    遍历每一个 dp[i][j],对于每对字符 dna1[i-1] 和 dna2[j-1],我们根据它们是否相等来决定是进行替换、插入、还是删除操作。如果字符相等,则不需要编辑操作,继承自 dp[i-1][j-1];否则,选择三种操作中的最小值。

完整代码

def solution(dna1, dna2):
    # 获取 dna1 和 dna2 的长度
    len1 = len(dna1)
    len2 = len(dna2)
    
    # 创建 dp 数组,dp[i][j] 表示 dna1[0..i-1] 和 dna2[0..j-1] 的最小编辑距离
    dp = [[0] * (len2 + 1) for _ in range(len1 + 1)]

    # 初始化 dp 数组
    for i in range(1, len1 + 1):
        dp[i][0] = i  # 删除所有字符
    for j in range(1, len2 + 1):
        dp[0][j] = j  # 插入所有字符

    # 填充 dp 数组
    for i in range(1, len1 + 1):
        for j in range(1, len2 + 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[len1][len2]

# 测试用例
if __name__ == "__main__":
    print(solution("AGT", "AGCT"))  # 输出 1
    print(solution("AACCGGTT", "AACCTTGG"))  # 输出 4
    print(solution("ACGT", "TGC"))  # 输出 3
    print(solution("A", "T"))  # 输出 1
    print(solution("GGGG", "TTTT"))  # 输出 4