题目解析:DNA序列编辑距离 | 豆包MarsCode AI刷题

56 阅读4分钟

问题描述

小R正在研究DNA序列,需要计算将一个受损的DNA序列(dna1)转换成一个未受损序列(dna2)所需的最少编辑步骤。编辑步骤包括增加、删除或替换一个碱基。这是一个经典的动态规划问题,被称为编辑距离问题。

题目分析

  • dna1:受损的DNA序列。
  • dna2:未受损的DNA序列。

目标是找到将dna1转换为dna2所需的最少编辑步骤。

思路解析

  1. 动态规划数组:我们使用一个二维数组dp来存储中间结果,其中dp[i][j]表示将dna1的前i个碱基转换为dna2的前j个碱基所需的最少编辑步骤。

  2. 初始化dp数组的第一行和第一列分别表示将dna1的前i个碱基转换为一个空序列,以及将一个空序列转换为dna2的前j个碱基所需的编辑步骤,这些值简单地等于它们各自的索引值,因为需要添加或删除相应数量的碱基。

  3. 填充数组:对于dp数组的其他元素,我们根据以下规则填充:

    • 如果dna1的第i个碱基与dna2的第j个碱基相同,那么dp[i][j]等于dp[i-1][j-1],因为不需要额外的编辑步骤。
    • 如果不相同,那么dp[i][j]等于dp[i-1][j](删除操作)、dp[i][j-1](插入操作)或dp[i-1][j-1](替换操作)中的最小值加一。
  4. 返回结果:最终,dp[m][n]将包含将整个dna1序列转换为整个dna2序列所需的最少编辑步骤。

算法详解与代码实现

  1. 创建并初始化二维数组: 我们创建一个(m+1) x (n+1)的二维数组dp,并初始化第一行和第一列为索引值,表示添加或删除碱基的步骤数。

    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
    
  2. 填充dp数组: 我们遍历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
    
  3. 返回最终结果dp[m][n]包含了将整个dna1序列转换为整个dna2序列所需的最少编辑步骤。

        return dp[m][n]
    
  4. 测试样例: 我们通过几个测试样例来验证算法的正确性。

    if __name__ == "__main__":
        print(solution("AGCTTAGC", "AGCTAGCT") == 2)
        print(solution("AGCCGAGC", "GCTAGCT") == 4)
    

核心代码详解

    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

这段代码是动态规划算法的核心部分,用于计算将受损DNA序列(dna1)转换成未受损序列(dna2)所需的最少编辑步骤。让我们逐步分析这个循环及其内部的逻辑。

  1. 循环结构

外层循环遍历dna1的每个碱基,从第二个碱基开始(因为第一个碱基在初始化时已经处理过):

for i in range(1, m + 1):

内层循环遍历dna2的每个碱基,同样从第二个碱基开始:

    for j in range(1, n + 1):
  1. 条件判断

对于每一对碱基,我们检查它们是否相同:

    if dna1[i-1] == dna2[j-1]:
  • 如果相同,这意味着当前碱基不需要任何编辑步骤。因此,将dp[i][j]设置为dp[i-1][j-1]的值,即不进行任何操作时的编辑步骤数。
  1. 编辑步骤计算

如果碱基不相同,我们需要考虑三种可能的编辑操作:

    else:
        dp[i][j] = min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]) + 1
  • dp[i-1][j]:删除dna1中的第i个碱基的编辑步骤数。
  • dp[i][j-1]:在dna1中插入一个与dna2的第j个碱基相同的碱基的编辑步骤数。
  • dp[i-1][j-1]:将dna1的第i个碱基替换为dna2的第j个碱基的编辑步骤数。

我们取这三个值中的最小值,并加1,因为我们需要额外的一步来执行当前的编辑操作。

个人思考

在解决这个问题时,我意识到动态规划是解决这类问题的有力工具。通过将问题分解为更小的子问题,并存储这些子问题的解,我们可以避免重复计算,从而提高算法的效率。这个问题也让我思考了如何将实际的生物学问题转化为计算机算法问题,并如何通过编程实现解决方案。通过这个问题,我加深了对动态规划和算法设计的理解。