青训营刷题记录

115 阅读4分钟

题目解析

题目描述

小R正在研究DNA序列,他需要一个函数来计算将一个受损的DNA序列(dna1)转换为一个未受损的序列(dna2)所需的最少编辑步骤。编辑步骤包括:

  • 增加一个碱基
  • 删除一个碱基
  • 替换一个碱基

解析

这是一个典型的动态规划(DP)问题。我们需要通过三种编辑方法来将dna1转化为dna2,即:

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

为了计算编辑步骤的最小值,我们可以使用一个二维数组 dp[i][j],表示将dna1的前i个字符转换为dna2的前j个字符所需的编辑步骤。

动态规划解法

1. 初始化DP数组

首先,我们需要初始化一个 dp 数组,其中 dp[i][j] 表示将 dna1 的前 i 个字符编辑为 dna2 的前 j 个字符所需的最小操作次数。

  • 边界条件

    • 如果 dna1 的前 i 个字符转换为一个空字符串 "",那么所需的操作次数是 i 次(删除操作)。
    • 如果 dna2 的前 j 个字符转换为空字符串 "",那么所需的操作次数是 j 次(插入操作)。

2. 状态转移

  • 当字符相同:如果 dna1[i-1] == dna2[j-1],不需要进行任何操作,那么:

    python
    複製程式碼
    dp[i][j] = dp[i-1][j-1]
    
  • 当字符不同:我们需要考虑三种操作:

    • 增加一个碱基:如果我们将 dna1 的前 i 个字符编辑为 dna2 的前 j-1 个字符,然后在末尾加一个碱基(即 dna2[j-1]),此时的操作次数为:

      python
      複製程式碼
      dp[i][j] = dp[i][j-1] + 1
      
    • 删除一个碱基:如果我们将 dna1 的前 i-1 个字符编辑为 dna2 的前 j 个字符,然后删除 dna1[i-1],此时的操作次数为:

      python
      複製程式碼
      dp[i][j] = dp[i-1][j] + 1
      
    • 替换一个碱基:如果我们将 dna1 的前 i-1 个字符编辑为 dna2 的前 j-1 个字符,然后替换 dna1[i-1]dna2[j-1],此时的操作次数为:

      python
      複製程式碼
      dp[i][j] = dp[i-1][j-1] + 1
      

因此,最终的状态转移公式为:

python
複製程式碼
dp[i][j] = min(dp[i-1][j] + 1,    # 删除
               dp[i][j-1] + 1,    # 插入
               dp[i-1][j-1] + 1)  # 替换

3. 最终结果

最终返回 dp[len1][len2],即将 dna1 转换为 dna2 所需的最小编辑步骤。

代码实现

python
複製程式碼
def solution(dna1, dna2):
    len1, len2 = len(dna1), len(dna2)

    # 创建DP数组,并初始化
    dp = [[0] * (len2 + 1) for _ in range(len1 + 1)]
    
    # 初始化边界条件
    for i in range(len1 + 1):
        dp[i][0] = i  # 删除所有字符
    for j in range(len2 + 1):
        dp[0][j] = j  # 添加所有字符
    
    # 填充DP表
    for i in range(1, len1 + 1):
        for j in range(1, len2 + 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[len1][len2]

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

代码解读

  1. 初始化

    • dp[i][0] 表示将 dna1 的前 i 个字符转为空字符串所需的删除次数,初始化为 i
    • dp[0][j] 表示将空字符串转为 dna2 的前 j 个字符所需的插入次数,初始化为 j
  2. 填充 DP 表

    • 对于每一对 ij,如果 dna1[i-1]dna2[j-1] 相同,不需要编辑,直接继承上一个状态:dp[i][j] = dp[i-1][j-1]
    • 如果不相同,则选择删除、插入或替换中的最小值作为最优编辑步骤。
  3. 返回结果

    • 最终的编辑步骤就是 dp[len1][len2],即将 dna1 转换为 dna2 所需的最小编辑次数。

测试用例

python
複製程式碼
print(solution("AGT", "AGCT"))  # 1
print(solution("AACCGGTT", "AACCTTGG"))  # 4
print(solution("ACGT", "TGC"))  # 3
print(solution("A", "T"))  # 1
print(solution("GGGG", "TTTT"))  # 4

小结

通过动态规划,我们能够高效地解决将一个DNA序列转换为另一个DNA序列所需的最少编辑步骤问题。通过构建一个二维数组来存储中间状态,并根据三种基本操作(插入、删除、替换)进行状态转移,最终得出最小编辑步骤。