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

115 阅读5分钟

题目回顾

image.png

题目分析

这个问题是关于计算两条DNA序列之间的最小变异次数,以反映它们之间的血缘关系。DNA序列由四种核苷酸组成:A(腺嘌呤)、C(胞嘧啶)、G(鸟嘌呤)和T(胸腺嘧啶)。变异可以通过三种方式发生:添加一个核苷酸、删除一个核苷酸或替换一个核苷酸。

题目要求你编写一个算法来计算两条DNA序列之间的最小变异次数。变异次数越少,表示两条DNA序列之间的血缘关系越近。

测试样例提供了不同情况下的DNA序列对,以及它们之间的最小变异次数。这些样例可以帮助你理解问题的具体要求和预期的输出结果。

分析题目时,可以考虑以下几点:

  1. 序列长度:如果两条序列长度不同,可能需要通过添加或删除核苷酸来使它们长度相同。

  2. 序列比较:对于长度相同的序列,可以通过比较每个位置上的核苷酸来确定是否需要替换。

  3. 动态规划:这个问题可以通过动态规划(DP)的方法来解决,其中DP表用于存储子问题的解,以避免重复计算。

  4. 边界条件:需要考虑序列长度为1或更短时的特殊情况。

  5. 算法效率:考虑到可能需要处理较长的序列,算法的效率和时间复杂度是一个重要的考虑因素。

通过这些分析,你可以更好地理解问题,并开始设计和实现解决方案。

代码实现

def solution(dna1, dna2):
    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
    
    # 填充动态规划矩阵
    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]

if __name__ == "__main__":
    #  You can add more test cases here
    print(solution("AGT", "AGCT") == 1)
    print(solution("", "ACGT") == 4)
    print(solution("GCTAGCAT", "ACGT") == 5)

一、功能概述 这段代码实现了计算两个 DNA 序列之间的编辑距离(Levenshtein distance)。编辑距离是指将一个字符串转换为另一个字符串所需的最少编辑操作次数,这里的编辑操作包括插入、删除和替换字符。

二、代码结构分析

  1. solution函数 - 参数: - dna1dna2:两个输入的 DNA 序列字符串。
    • 初始化动态规划矩阵
      • m, n = len(dna1), len(dna2):获取两个 DNA 序列的长度。
      • dp = [[0] * (n + 1) for _ in range(m + 1)]:创建一个二维列表dp,用于存储动态规划过程中的中间结果。dp[i][j]表示将dna1的前i个字符转换为dna2的前j个字符所需的最少编辑操作次数。
    • 初始化第一行和第一列
      • 对于第一行dp[i][0] = i,表示将空字符串转换为dna1的前i个字符需要i次插入操作。
      • 对于第一列dp[0][j] = j,表示将dna2的前j个字符转换为空字符串需要j次删除操作。
    • 填充动态规划矩阵
      • 对于两个序列的非空部分,遍历每个字符位置。如果当前位置的字符相等,即dna1[i - 1] == dna2[j - 1],则不需要进行编辑操作,dp[i][j]的值等于dp[i - 1][j - 1]
    • 如果当前位置的字符不相等,则需要考虑三种编辑操作:
      • 删除操作:dp[i - 1][j] + 1,即先将dna1的前i - 1个字符转换为dna2的前j个字符,然后删除dna1的第i个字符。
      • 插入操作:dp[i][j - 1] + 1,即先将dna1的前i个字符转换为dna2的前j - 1个字符,然后在dna1的后面插入dna2的第j个字符。
      • 替换操作:dp[i - 1][j - 1] + 1,即先将dna1的前i - 1个字符转换为dna2的前j - 1个字符,然后将dna1的第i个字符替换为dna2的第j个字符。
      • 取这三种操作中的最小值作为dp[i][j]的值。
    • 返回结果
      • 最终返回dp[m][n],即把整个dna1转换为dna2所需的最少编辑操作次数。 2. if __name__ == "__main__":部分 - 这部分用于测试代码,给出了三个测试用例,并打印出函数调用的结果与预期结果是否相等。

三、时间和空间复杂度

  1. 时间复杂度
    • 代码中有三层嵌套的循环,分别是初始化第一行和第一列的两个循环,以及填充动态规划矩阵的两个嵌套循环。因此,时间复杂度为 O(mn)O(mn),其中mn分别是两个 DNA 序列的长度。
  2. 空间复杂度
    • 代码中创建了一个大小为(m + 1) * (n + 1)的二维列表dp来存储中间结果。因此,空间复杂度为 O(mn)O(mn)