DNA序列还原 | 豆包MarsCode AI刷题

111 阅读4分钟

DNA序列还原

问题描述

给定一段受损的 DNA 碱基序列 dna1,在每次只操作一个碱基的情况下,将其以最少的操作步骤将其还原到未受损的 DNA 碱基序列 dna2。

只可以对 DNA 碱基序列中的一个碱基进行三种操作:

  1. 增加一个碱基
  2. 去除一个碱基
  3. 替换一个碱基

输入描述:

输入两段 DNA 碱基序列,每段分一行输入

第一行为第一段受损的 DNA 碱基序列 dna1

第二行为第二段未受损的 DNA 碱基序列 dna2

输出描述:

最小操作步骤数

备注:

0 <= dna1.length, dna2.length <= 500

dna1 和 dna2 由大写英文字母 A、G、C、T 组成

示例 1

输入

AGCTTAGC

AGCTAGCT

输出

2

说明

AGCTTAGC -> AGCTAGC(删除 T)

AGCTAGC -> AGCTAGCT(增加 T)

示例 2

输入

AGCCGAGC

GCTAGCT

输出

4

说明

AGCCGAGC -> GCCGAGC(删除 A)

GCCGAGC -> GCTGAGC(将 C 替换为 T)

GCTGAGC -> GCTAGC(删除 G)

GCTAGC -> GCTAGCT(增加 T)

要解决这个问题,我们可以使用动态规划(Dynamic Programming, DP)的方法。这个方法非常适合解决涉及字符串编辑距离的问题,比如 DNA 碱基序列的转换。我们将使用经典的编辑距离算法(也称为莱文斯坦距离算法)。

动态规划方法

我们定义一个二维数组 dp,其中 dp[i][j] 表示将 dna1 的前 i 个字符转换为 dna2 的前 j 个字符所需的最小操作数。

状态转移方程

  1. 初始状态

    • dp[0][0] = 0,因为将空字符串转换为空字符串不需要任何操作。
    • dp[i][0] = i,因为将前 i 个字符的 dna1 转换为空字符串需要 i 次删除操作。
    • dp[0][j] = j,因为将空字符串转换为前 j 个字符的 dna2 需要 j 次插入操作。
  2. 状态转移方程

    • 如果 dna1[i-1] == dna2[j-1],则 dp[i][j] = dp[i-1][j-1],因为最后一个字符相同,不需要额外操作。

    • 如果

      dna1[i-1] != dna2[j-1]
      

      ,则需要考虑三种操作:

      • 插入操作:dp[i][j] = dp[i][j-1] + 1
      • 删除操作:dp[i][j] = dp[i-1][j] + 1
      • 替换操作:dp[i][j] = dp[i-1][j-1] + 1 要解决将受损的 DNA 序列恢复到原始序列的问题,我们可以使用动态规划的方法。这是一个典型的编辑距离问题,也称为莱文斯坦距离问题。

问题理解

给定两个 DNA 序列:

  • dna1:受损的序列。
  • dna2:原始的序列。

我们的目标是通过最少的操作次数将 dna1 转换为 dna2。允许的操作有:

  1. 插入一个碱基。
  2. 删除一个碱基。
  3. 替换一个碱基。

动态规划方法

我们使用一个二维数组 dp,其中 dp[i][j] 表示将 dna1 的前 i 个字符转换为 dna2 的前 j 个字符所需的最小操作数。

初始化
  1. 基本情况

    • dp[0][0] = 0:空字符串转为空字符串不需要操作。
    • dp[i][0] = i:将 dna1 的前 i 个字符转为空字符串需要 i 次删除。
    • dp[0][j] = j:空字符串转为 dna2 的前 j 个字符需要 j 次插入。
状态转移

对于每一对索引 ii 和 jj,我们计算 dp[i][j]

  • 如果当前字符相同(dna1[i-1] == dna2[j-1]),则不需要额外操作:dp[i][j] = dp[i-1][j-1]

  • 如果字符不同(dna1[i-1] != dna2[j-1]),需要考虑三种操作:

    1. 插入dp[i][j] = dp[i][j-1] + 1
    2. 删除dp[i][j] = dp[i-1][j] + 1
    3. 替换dp[i][j] = dp[i-1][j-1] + 1

公式为:

复杂度

该算法的时间复杂度是 O(m×n)O(m×n),其中 mmnn 分别是 dna1dna2 的长度。空间复杂度也是 O(m×n)O(m×n),因为需要存储 dp 表。

  • 综合起来就是:
  • dp[i][j]=min(dp[i1][j]+1,dp[i][j1]+1,dp[i1][j1]+(dna1[i1]dna2[j1]))dp[i][j] = \min(dp[i-1][j] + 1, dp[i][j-1] + 1, dp[i-1][j-1] + (dna1[i-1] \neq dna2[j-1]))
def solution(dna1, dna2):
    m, n = len(dna1), len(dna2)
    dp = [[0] * (n + 1) for _ in range(m + 1)]
    
    for i in range(1, m+1):
        dp[i][0] = i
    for j in range(1, n+1):
        dp[0][j] = j

    dp[0][0] = 0
    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] + 1, dp[i][j-1] + 1, dp[i-1][j-1] + 1)
    
    # Please write your code here
    return dp[m][n]

if __name__ == "__main__":
    #  You can add more test cases here
    print(solution("AGCTTAGC", "AGCTAGCT") == 2 )
    print(solution("AGCCGAGC", "GCTAGCT") == 4)