编号25题解 | 豆包MarsCode AI 刷题

27 阅读5分钟

题目描述

计算将一个受损DNA序列转换成未受损序列所需的最少编辑步骤

给定两个字符串 dna1dna2,分别表示受损的DNA序列和未受损的DNA序列。任务是编写一个函数,计算将 dna1 转换成 dna2 所需的最少编辑步骤。

解题思路

这个问题可以通过动态规划来解决。动态规划的思想是将问题分解成子问题,并利用子问题的解来构建原问题的解。具体来说,我们可以定义一个二维数组 dp,其中 dp[i][j] 表示将 dna1 的前 i 个字符转换成 dna2 的前 j 个字符所需的最少编辑步骤。

动态规划的核心思想
  1. 初始化

    • dp[i][0] 表示将 dna1 的前 i 个字符转换成空字符串所需的步骤,即删除所有字符,因此 dp[i][0] = i
    • dp[0][j] 表示将空字符串转换成 dna2 的前 j 个字符所需的步骤,即插入所有字符,因此 dp[0][j] = j
  2. 状态转移

    • 如果 dna1[i-1] == dna2[j-1],则 dp[i][j] = dp[i-1][j-1],即无需编辑。

    • 否则,dp[i][j] 可以通过以下三种方式之一获得最小值:

      • 删除 dna1 的第 i 个字符:dp[i-1][j] + 1
      • 插入 dna2 的第 j 个字符:dp[i][j-1] + 1
      • 替换 dna1 的第 i 个字符为 dna2 的第 j 个字符:dp[i-1][j-1] + 1
  3. 最终结果

    • dp[m][n] 即为将 dna1 转换成 dna2 所需的最少编辑步骤,其中 m 和 n 分别是 dna1 和 dna2 的长度。

详细步骤

  1. 初始化

    • 创建一个 (m+1) x (n+1) 的二维数组 dp,其中 m 是 dna1 的长度,n 是 dna2 的长度。

    • 初始化第一行和第一列:

      • dp[i][0] = i,表示将 dna1 的前 i 个字符转换成空字符串所需的步骤。
      • dp[0][j] = j,表示将空字符串转换成 dna2 的前 j 个字符所需的步骤。
  2. 填充DP表

    • 遍历 dna1 和 dna2 的每个字符,根据状态转移方程更新 dp 表。
  3. 返回结果

    • 返回 dp[m][n],即为最终结果。

代码解析

def solution(dna1: str, dna2: str) -> int:
    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  # 插入所有字符
    
    # 填充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] + 1,      # 删除一个字符
                    dp[i][j - 1] + 1,      # 插入一个字符
                    dp[i - 1][j - 1] + 1   # 替换一个字符
                )
    
    return dp[m][n]

输入输出全过程分析实例

示例 1

输入dna1 = "AGCTTAGC", dna2 = "AGCTAGCT"

  1. 初始化

    • m = 8n = 8
    • dp 表初始化为 (9 x 9) 的零矩阵。
  2. 初始化第一行和第一列

    • dp[i][0] = i,表示将 dna1 的前 i 个字符转换成空字符串所需的步骤。
    • dp[0][j] = j,表示将空字符串转换成 dna2 的前 j 个字符所需的步骤。
  3. 填充DP表

    • i = 1j = 1dna1[0] == dna2[0]dp[1][1] = dp[0][0] = 0
    • i = 1j = 2dna1[0] != dna2[1]dp[1][2] = min(dp[0][2] + 1, dp[1][1] + 1, dp[0][1] + 1) = 1
    • i = 1j = 3dna1[0] != dna2[2]dp[1][3] = min(dp[0][3] + 1, dp[1][2] + 1, dp[0][2] + 1) = 2
    • ...
    • i = 8j = 8dna1[7] == dna2[7]dp[8][8] = dp[7][7] = 2
  4. 返回结果

    • return 2

输出2

示例 2

输入dna1 = "AGCCGAGC", dna2 = "GCTAGCT"

  1. 初始化

    • m = 8n = 7
    • dp 表初始化为 (9 x 8) 的零矩阵。
  2. 初始化第一行和第一列

    • dp[i][0] = i,表示将 dna1 的前 i 个字符转换成空字符串所需的步骤。
    • dp[0][j] = j,表示将空字符串转换成 dna2 的前 j 个字符所需的步骤。
  3. 填充DP表

    • i = 1j = 1dna1[0] != dna2[0]dp[1][1] = min(dp[0][1] + 1, dp[1][0] + 1, dp[0][0] + 1) = 1
    • i = 1j = 2dna1[0] != dna2[1]dp[1][2] = min(dp[0][2] + 1, dp[1][1] + 1, dp[0][1] + 1) = 2
    • i = 1j = 3dna1[0] == dna2[2]dp[1][3] = dp[0][2] = 2
    • ...
    • i = 8j = 7dna1[7] != dna2[6]dp[8][7] = min(dp[7][7] + 1, dp[8][6] + 1, dp[7][6] + 1) = 4
  4. 返回结果

    • return 4

输出4

学习心得

  1. 动态规划的思想:动态规划是一种通过将问题分解成子问题来解决问题的方法。在这个问题中,我们将整个字符串的编辑距离问题分解成一个个字符的编辑距离问题,从而高效地解决了问题。
  2. 状态转移方程:状态转移方程是动态规划的核心,它描述了如何从已知的状态推导出新的状态。在这个问题中,我们通过比较当前字符是否相等,来决定编辑步骤的最小值。
  3. 初始化的重要性:初始化是动态规划的第一步,正确的初始化可以确保后续的计算是正确的。在这个问题中,我们初始化了第一行和第一列,表示将一个字符串转换成空字符串或从空字符串转换成另一个字符串所需的步骤。