题目解析:计算DNA序列之间的最小变异次数| 豆包MarsCode AI刷题

89 阅读5分钟

题目解析:计算DNA序列之间的最小变异次数

在这道题目中,我们需要帮助小U计算两条DNA序列之间的最小变异次数。DNA序列由字符 ACGT 组成,变异操作包括三种:插入删除替换。最小变异次数反映了将一个DNA序列转换为另一个DNA序列所需的最少操作次数。

解题思路

这道题是典型的编辑距离问题。编辑距离问题的核心是通过一系列操作,将一个字符串转换为另一个字符串,而这系列操作的代价(即操作次数)最小。编辑距离的计算通常通过动态规划(Dynamic Programming,DP)实现。

  1. 定义状态

    • dp[i][j] 表示将 dna1[0..i-1] 转换为 dna2[0..j-1] 的最小操作次数。
  2. 初始化边界条件

    • i=0 时,表示将空字符串 "" 转换为 dna2[0..j-1],需要 j 次插入操作。
    • j=0 时,表示将空字符串 "" 转换为 dna1[0..i-1],需要 i 次删除操作。
  3. 状态转移

    • 如果 dna1[i-1] == dna2[j-1],则两个字符相同,不需要变异,dp[i][j] = dp[i-1][j-1]

    • 否则,我们选择三种操作中最小的:

      • 删除操作dp[i-1][j] + 1
      • 插入操作dp[i][j-1] + 1
      • 替换操作dp[i-1][j-1] + 1
  4. 返回结果

    • 最终的编辑距离就是 dp[len(dna1)][len(dna2)],表示将整个 dna1 转换为 dna2 的最小操作次数。

代码实现

def solution(dna1, dna2):
    # 初始化一个二维数组 dp,大小为 (len(dna1) + 1) x (len(dna2) + 1)
    dp = [[0] * (len(dna2) + 1) for _ in range(len(dna1) + 1)]
    
    # 初始化边界条件
    for i in range(len(dna1) + 1):
        dp[i][0] = i  # 如果 dna2 为空,变异次数就是 dna1 的长度
    for j in range(len(dna2) + 1):
        dp[0][j] = j  # 如果 dna1 为空,变异次数就是 dna2 的长度
    
    # 填充 dp 数组
    for i in range(1, len(dna1) + 1):
        for j in range(1, len(dna2) + 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[len(dna1)][len(dna2)]

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

代码解析

  1. 初始化 DP 数组

    • 我们首先初始化一个二维数组 dp,它的大小为 (len(dna1) + 1) x (len(dna2) + 1),用来记录每个子问题的最小编辑距离。
    • 对于边界情况,我们初始化第一行和第一列,分别代表从空字符串转换为另一个字符串的最小操作次数。
  2. 状态转移

    • dna1[i-1] == dna2[j-1] 时,两个字符相同,不需要变异,dp[i][j] = dp[i-1][j-1]
    • 否则,取三种操作中的最小值:删除、插入、替换。
  3. 返回结果

    • 最终的结果是 dp[len(dna1)][len(dna2)],它表示将整个 dna1 转换为 dna2 所需的最小变异次数。

时间复杂度分析

由于我们使用了动态规划,需要填充一个大小为 (len(dna1) + 1) x (len(dna2) + 1) 的二维数组。因此,时间复杂度为 O(n * m) ,其中 ndna1 的长度,mdna2 的长度。这种解法在实际应用中非常高效,能够处理较大规模的输入。

知识总结:动态规划的应用

在解决类似编辑距离的问题时,动态规划是一个非常有用的技巧。通过将问题分解为较小的子问题,并使用之前计算过的结果来避免重复计算,可以大大提高算法的效率。动态规划的核心思想是通过状态转移公式来构建一个解空间,并逐步求解最终的结果。

学习总结与建议

通过这道题目,我进一步加深了对动态规划的理解,特别是如何将问题拆解成子问题并求解。在实际的刷题过程中,动态规划的问题往往需要大量的练习才能掌握。对于初学者,我的建议是:

  1. 多做练习:动态规划的解法比较抽象,需要通过大量的实践来掌握。
  2. 思考状态转移:每次遇到动态规划问题时,先思考如何将问题拆解成更小的子问题,并通过状态转移公式求解。
  3. 总结并归纳:动态规划的题目往往具有相似的解法模板,总结常见的模板会帮助你更高效地解决问题。

高效学习方法

结合豆包MarsCode AI刷题功能,我认为可以采用以下策略提高学习效率:

  1. 错题本:遇到不懂的题目,可以将其添加到错题本中,定期回顾,确保掌握解决问题的方法。
  2. 按类型刷题:可以按照题目类型(如动态规划、贪心算法、图论等)进行分类练习,这样有助于系统化学习。
  3. 多做变式题目:通过多做变式题目,帮助自己巩固基础,同时提升应对复杂问题的能力。

通过合理规划学习路径,结合错题分析与总结,不仅能提升自己的算法能力,还能在短时间内积累大量实战经验。