刷题实践选题:古生物DNA序列血缘分析

76 阅读3分钟

def solution(dna1, dna2): m, n = len(dna1), len(dna2) # 创建一个 (m+1) x (n+1) 的二维数组 dp dp = [[0] * (n + 1) for _ in range(m + 1)] # 初始化 dp 数组 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): # 计算插入、删除和替换的代价 insert_cost = dp[i][j-1] + 1 delete_cost = dp[i-1][j] + 1 replace_cost = dp[i-1][j-1] + (1 if dna1[i-1] != dna2[j-1] else 0) # 取最小值 dp[i][j] = min(insert_cost, delete_cost, replace_cost) # 返回最终结果 return dp[m][n] if name == "main": # You can add more test cases here print(solution("AGT", "AGCT") == 1) print(solution("", "ACGT") == 4) print(solution("GCTAGCAT", "ACGT") == 5) 这段代码实现了编辑距离(Levenshtein Distance)算法,用于计算两个DNA序列(字符串)之间的最小编辑操作次数。编辑操作包括插入、删除和替换。

逐步分析:

  1. 初始化与创建二维数组

    • m 和 n 分别是 dna1 和 dna2 的长度。
    • 创建了一个大小为 (m+1) x (n+1) 的二维数组 dp,用来存储从子问题到更大问题的解决方案。dp[i][j] 存储的是将 dna1 的前 i 个字符转化为 dna2 的前 j 个字符所需要的最小编辑操作数。
  2. 初始化边界条件

    • 第一行和第一列的初始化表示将一个空字符串转化为另一个字符串的编辑距离。也就是说:

      • dp[i][0] = i 表示将 dna1 的前 i 个字符转换为空字符串需要 i 次删除操作。
      • dp[0][j] = j 表示将空字符串转换为 dna2 的前 j 个字符需要 j 次插入操作。
  3. 填充 dp 数组

    • 对每个 i, j,计算编辑操作的代价:

      • insert_cost = dp[i][j-1] + 1 表示通过插入一个字符将 dna1 的前 i 个字符转化为 dna2 的前 j 个字符。
      • delete_cost = dp[i-1][j] + 1 表示通过删除一个字符将 dna1 的前 i 个字符转化为 dna2 的前 j 个字符。
      • replace_cost = dp[i-1][j-1] + (1 if dna1[i-1] != dna2[j-1] else 0) 表示替换操作,如果当前字符不相同则增加代价。
  4. 取最小值

    • dp[i][j] = min(insert_cost, delete_cost, replace_cost) 取插入、删除、替换三者中的最小代价,确保每一步都选择最优操作。
  5. 返回最终结果

    • 最终的编辑距离结果保存在 dp[m][n] 中,表示将 dna1 转换为 dna2 的最小编辑操作次数。

测试:

  • solution("AGT", "AGCT") 返回 1,因为只需插入一个字符 C
  • solution("", "ACGT") 返回 4,因为需要插入 4 个字符。
  • solution("GCTAGCAT", "ACGT") 返回 5,这是这两个字符串之间的最小编辑距离。

优化:

6.这段代码的时间复杂度为 O(m*n),空间复杂度也是 O(m*n)。对于较长的字符串,可以考虑使用动态规划的滚动数组优化空间复杂度。