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

48 阅读4分钟

「81.古生物DNA序列血缘分析」

问题描述 小U作为古生物学家,在研究不同古生物的血缘关系时,需通过比较它们的DNA序列来分析。DNA由A、C、G、T四种核苷酸组成,且存在添加、删除、替换一个核苷酸这三种变异方式。任务是编写算法计算两条给定DNA序列(dna1和dna2)之间所需的最小变异次数,变异次数越少意味着血缘关系越近。

思路解析

  1. 理解变异方式与目标 明确DNA序列可通过添加、删除、替换核苷酸这三种方式发生变异,我们的目标是找到使两条DNA序列相同所需的最少变异操作次数,以此来反映它们之间的血缘关系。
  2. 确定解题方法 可以使用动态规划的思想来解决此问题。通过构建一个二维数组来记录在不同子序列情况下的最小变异次数。具体来说,对于dna1的前i个字符和dna2的前j个字符组成的子序列,我们逐步计算使其相同的最小变异次数。

解题步骤

  1. 创建二维数组:
    • 创建一个大小为(len(dna1) + 1)×(len(dna2) + 1)的二维数组dp,用于存储中间结果。其中dp[i][j]表示将dna1的前i个字符和dna2的前j个字符变为相同所需的最小变异次数。
    • 初始化dp数组的边界情况:
      • dp[0][0] = 0,即空的dna1序列和空的dna2序列不需要任何变异操作,变异次数为0。
      • 对于i > 0,dp[i][0] = i,表示将dna1的前i个字符变为空序列(即删除i个字符)所需的变异次数为i。
      • 对于j > 0,dp[0][j] = j,表示将空的dna1序列变为dna2的前j个字符(即添加j个字符)所需的变异次数为j。
  2. 填充二维数组:
    • 从dp[1][1]开始,通过两层循环遍历dna1和dna2的字符:
      • 外层循环遍历dna1的字符,变量i从1到len(dna1)。
      • 内层循环遍历dna2的字符,变量j从1到len(dna2)。
    • 对于每个dp[i][j],根据dna1的第i个字符和dna2的第j个字符是否相同来分情况讨论:
      • 如果dna1[i - 1] == dna2[j - 1](注意数组下标从0开始,所以这里是i - 1和j - 1),则dp[i][j] = dp[i - 1][j - 1],即当前字符相同,不需要进行任何变异操作,最小变异次数与前一个子序列相同。
      • 如果dna1[i - 1]!= dna2[j - 1],则需要考虑三种变异操作,取最小值:
        • 替换操作:dp[i][j] = dp[i - 1][j - 1] + 1,即在前一个子序列基础上替换当前字符,变异次数加1。
        • 删除操作:dp[i][j] = dp[i - 1][j] + 1,即删除dna1的第i个字符,变异次数加1。
        • 添加操作:dp[i][j] = dp[i][j - 1] + 1,即添加一个字符使其与dna2的第j个字符相同,变异次数加1。
        • 最终dp[i][j]取这三种情况中的最小值。
  3. 获取结果:
    • 填充完二维数组后,dp[len(dna1)][len(dna2)]的值就是将整个dna1序列和整个dna2序列变为相同所需的最小变异次数,将其作为最终答案返回。

代码实现

def min_mutation_times(dna1, dna2):
    m = len(dna1)
    n = 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:
                replace = dp[i - 1][j - 1] + 1
                delete = dp[i - 1][j] + 1
                add = dp[i][j - 1] + 1
                dp[i][j] = min(replace, delete, add)

    return dp[m][n]

代码解读

  • 首先定义了函数min_mutation_times,它接受两条DNA序列dna1和dna2作为参数。
  • 在函数内部,先获取两条DNA序列的长度m和n。
  • 然后创建了二维数组dp,并按照上述步骤初始化了边界情况。
  • 接着通过两层循环遍历dna1和dna2的字符,根据字符是否相同分情况填充dp数组。
  • 最后返回dp[m][n],即两条DNA序列变为相同所需的最小变异次数。

复杂度分析

  • 时间复杂度:
    • 外层循环遍历dna1的长度m次,内层循环对于每个外层循环的情况遍历dna2的长度n次,所以总共需要执行m×n次循环操作。在每次循环中,主要操作是比较字符和取最小值,时间复杂度可看作常数时间。因此,时间复杂度为O(mn)O(mn),其中m是dna1的长度,n是dna2的长度。
  • 时间复杂度:
    • 主要的空间消耗在于创建的二维数组dp,其大小为(m + 1)×(n + 1),所以空间复杂度为O(mn)O(mn)