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

32 阅读3分钟

问题描述

给定两个DNA序列 dna1dna2,需要通过最少的编辑操作将 dna1 转换为 dna2。可用的编辑操作有三种:

  1. 插入一个碱基(Insert):在 dna1 中的某个位置插入一个碱基。
  2. 删除一个碱基(Delete):删除 dna1 中的某个碱基。
  3. 替换一个碱基(Replace):将 dna1 中的某个碱基替换为 dna2 中对应位置的碱基。

目标是计算最少的编辑步骤数。

解题思路

这道题实际上是 编辑距离(Levenshtein Distance) 问题的变体。可以使用动态规划(Dynamic Programming,简称 DP)来解决。编辑距离的核心思想是,通过逐步比较两个字符串(这里是DNA序列),计算从一个字符串转换为另一个字符串所需的最小操作数。
具体步骤如下:

  1. 定义状态
    dp[i][j] 表示将 dna1 的前 i 个字符转换为 dna2 的前 j 个字符所需的最少编辑步骤。
  2. 初始化边界条件
    • dp[i][0] 表示将 dna1 的前 i 个字符转换为空字符串,需要 i 次删除操作。
    • dp[0][j] 表示将空字符串转换为 dna2 的前 j 个字符,需要 j 次插入操作。
  3. 状态转移
    • 如果 dna1[i-1] == dna2[j-1],则不需要进行任何操作,dp[i][j] = dp[i-1][j-1]
    • 如果 dna1[i-1] != dna2[j-1],则有三种可能的操作:
      • 删除:删除 dna1[i-1],即 dp[i][j] = dp[i-1][j] + 1
      • 插入:在 dna1[i-1] 后插入 dna2[j-1],即 dp[i][j] = dp[i][j-1] + 1
      • 替换:将 dna1[i-1] 替换为 dna2[j-1],即 dp[i][j] = dp[i-1][j-1] + 1
    • 取这三者中的最小值作为 dp[i][j]
  4. 目标结果
    最终的答案就是 dp[m][n],其中 mn 分别是 dna1dna2 的长度。

动态规划实现

def solution(dna1, dna2):
    m, n = len(dna1), len(dna2)
    
    # 初始化 dp 数组,大小为 (m+1) x (n+1)
    dp = [[0] * (n + 1) for _ in range(m + 1)]
    
    # 初始化边界条件
    for i in range(m + 1):
        dp[i][0] = i  # 将 dna1 转为空字符串的操作数
    for j in range(n + 1):
        dp[0][j] = j  # 将空字符串转为 dna2 的操作数
    
    # 动态规划填充 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:
                dp[i][j] = min(dp[i - 1][j],     # 删除
                               dp[i][j - 1],     # 插入
                               dp[i - 1][j - 1]) + 1  # 替换
    
    return dp[m][n]

# 测试案例
if __name__ == "__main__":
    # 测试案例
    print(solution("AGCTTAGC", "AGCTAGCT") == 2)  # 应输出 True
    print(solution("AGCCGAGC", "GCTAGCT") == 4)  # 应输出 True
    print(solution("AGT", "AGCT") == 1)  # 应输出 True
    print(solution("AACCGGTT", "AACCTTGG") == 4)  # 应输出 True
    print(solution("ACGT", "TGC") == 3)  # 应输出 True
    print(solution("A", "T") == 1)  # 应输出 True
    print(solution("GGGG", "TTTT") == 4)  # 应输出 True

复杂度分析

  • 时间复杂度O(m * n),其中 mn 分别是 dna1dna2 的长度。因为我们需要填充一个大小为 (m+1) x (n+1) 的 DP 数组,并且每个位置的计算都需要常数时间。
  • 空间复杂度O(m * n),我们需要一个大小为 (m+1) x (n+1) 的二维数组来存储中间结果。

总结

通过使用动态规划解决DNA序列的编辑问题,可以有效地计算出从一个DNA序列转换到另一个序列所需的最小编辑步骤。该方法具有较高的时间和空间复杂度,但适用于中等规模的输入。在实际应用中,我们可以通过改进空间复杂度(例如使用滚动数组)来优化内存使用。