古生物DNA序列血缘分析解析| 豆包MarsCode AI刷题

103 阅读6分钟

问题描述

小U是一位古生物学家,正在研究不同物种之间的血缘关系。为了分析两种古生物的血缘远近,她需要比较它们的DNA序列。DNA由四种核苷酸A、C、G、T组成,并且可能通过三种方式发生变异:添加一个核苷酸、删除一个核苷酸或替换一个核苷酸。小U认为两条DNA序列之间的最小变异次数可以反映它们之间的血缘关系:变异次数越少,血缘关系越近。

你的任务是编写一个算法,帮助小U计算两条DNA序列之间所需的最小变异次数。

代码展示

    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):
            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
    
    # 返回最终结果
    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)

代码解析

  1. 函数定义

    def solution(dna1, dna2):
    

    定义了一个名为 solution 的函数,接受两个参数 dna1dna2,分别表示两个DNA序列。

  2. 获取长度

    m, n = len(dna1), len(dna2)
    

    获取 dna1dna2 的长度,分别存储在 mn 中。

  3. 创建二维数组 dp

    dp = [[0] * (n + 1) for _ in range(m + 1)]
    

    创建一个 (m+1) x (n+1) 的二维数组 dp,用于存储动态规划的状态。dp[i][j] 表示 dna1 的前 i 个字符和 dna2 的前 j 个字符之间的最小编辑距离。

  4. 初始化 dp 数组的第一行和第一列

    for i in range(m + 1):
        dp[i][0] = i
    for j in range(n + 1):
        dp[0][j] = j
    
    • dp[i][0] 表示将 dna1 的前 i 个字符转换为空字符串,需要 i 次删除操作。
    • dp[0][j] 表示将空字符串转换为 dna2 的前 j 个字符,需要 j 次插入操作。
  5. 填充 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
    
    • 如果 dna1[i-1] == dna2[j-1],则 dp[i][j] = dp[i-1][j-1],因为不需要任何编辑操作。
    • 否则,dp[i][j] = min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]) + 1,分别对应删除、插入和替换操作。
  6. 返回最终结果

    return dp[m][n]
    

    返回 dp[m][n],即 dna1dna2 之间的最小编辑距离。

  7. 测试用例

    if __name__ == "__main__":
        print(solution("AGT", "AGCT") == 1)
        print(solution("", "ACGT") == 4)
        print(solution("GCTAGCAT", "ACGT") == 5)
    

    __main__ 块中,添加了一些测试用例来验证代码的正确性。

总结

这段代码使用动态规划的方法来计算两个DNA序列之间的最小编辑距离。通过初始化一个二维数组 dp,并根据状态转移方程逐步填充 dp 数组,最终得到最小编辑距离。

知识点总结

  1. 动态规划(Dynamic Programming)

    • 概念:动态规划是一种通过将复杂问题分解为更小的子问题来解决的方法。它通常用于优化问题,其中子问题的解可以被存储和重用。
    • 应用:在这个问题中,我们使用动态规划来计算两个DNA序列之间的最小编辑距离。通过构建一个二维数组 dp,我们逐步计算每个子问题的最优解,最终得到整个问题的最优解。
  2. 二维数组(2D Array)

    • 概念:二维数组是一个数组的数组,可以用来表示矩阵或表格形式的数据结构。
    • 应用:在这个问题中,我们使用二维数组 dp 来存储每个子问题的最小编辑距离。dp[i][j] 表示 dna1 的前 i 个字符和 dna2 的前 j 个字符之间的最小编辑距离。
  3. 状态转移方程(State Transition Equation)

    • 概念:状态转移方程描述了如何从一个状态转移到另一个状态。在动态规划中,状态转移方程是解决问题的关键。

    • 应用:在这个问题中,状态转移方程如下:

      • 如果 dna1[i-1] == dna2[j-1],则 dp[i][j] = dp[i-1][j-1]
      • 否则,dp[i][j] = min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]) + 1
  4. 初始化(Initialization)

    • 概念:初始化是指在开始计算之前,为某些变量或数据结构设置初始值。
    • 应用:在这个问题中,我们初始化了 dp 数组的第一行和第一列。dp[i][0] 和 dp[0][j] 分别表示将 dna1 的前 i 个字符转换为空字符串和将空字符串转换为 dna2 的前 j 个字符所需的操作次数。
  5. 最小值函数(min function)

    • 概念min 函数用于返回一组值中的最小值。
    • 应用:在这个问题中,我们使用 min 函数来选择删除、插入和替换操作中的最小值,并加上1作为当前操作的代价。
  6. 边界条件(Boundary Conditions)

    • 概念:边界条件是指在问题的边界上需要特殊处理的情况。
    • 应用:在这个问题中,边界条件包括 dp[i][0] 和 dp[0][j] 的初始化,分别表示将 dna1 的前 i 个字符转换为空字符串和将空字符串转换为 dna2 的前 j 个字符所需的操作次数。

总结

这段代码主要使用了动态规划的思想来解决两个DNA序列之间的最小编辑距离问题。通过构建一个二维数组 dp,并使用状态转移方程逐步填充 dp 数组,最终得到最小编辑距离。涉及到的知识点包括动态规划、二维数组、状态转移方程、初始化和边界条件。

利用AI的学习方法

  1. 利用AI进行思路引导

    • 在刷题过程中,如果遇到难题,可以先让AI给出思路提示,帮助你理解题目。
    • 如果没有思路,可以继续让AI给出更详细的代码提示,逐步完善你的代码。
  2. 实时分析与错误诊断

    • AI可以对开发者的答题情况进行实时分析,及时指出错误并提供详细解析,帮助快速掌握正确的解题思路和方法。
  3. 集成IDE功能

    • 一些AI刷题平台还集成了IDE功能,比如语法检查和代码调试,这样可以在编写代码的同时得到即时反馈。
  4. 逐步学习和实践

    • 拿到题目后,先让AI帮助你理解题目,然后给出初步代码,最后自己修改和完善代码,并让AI帮你检查代码是否正确。
  5. 扩展和优化

    • 利用AI刷题时,不仅要理解AI给出的代码,还应该尝试自己进行调试和优化,以提升编程能力。
  6. 善用AI作为编程助手

    • 把AI当作一个时刻在线的编程助手,用好AI可以更快更好地理解和掌握算法,提高编程能力和效率。