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

53 阅读3分钟

题目背景:

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

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

思路分析

问题定义:

输入:两条DNA序列 dna1 和 dna2。

输出:两条DNA序列之间的最小变异次数。

变异类型:
  • 添加一个核苷酸。
  • 删除一个核苷酸。
  • 替换一个核苷酸。
算法选择:
  • 使用动态规划(Dynamic Programming, DP)来解决这个问题。动态规划是一种通过将问题分解为子问题,并存储子问题的解来避免重复计算的方法。
状态定义:

dp[i][j] 表示dna1的前i个字符和dna2的前j个字符之间的最小变异次数。

状态转移方程:
  • 如果 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
边界条件:
  • dp[i][0] 表示 dna1 的前 i 个字符和空字符串之间的最小变异次数,即 i
  • dp[0][j] 表示空字符串和dna2的前j个字符之间的最小变异次数,即 j
代码详解
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], dp[i][j - 1], dp[i - 1][j - 1]) + 1
    
    return dp[m][n]
代码解析
初始化:
  • dp 是一个二维数组,用于存储子问题的解。
  • dp[i][0]dp[0][j]分别初始化为 ij,表示添加或删除字符的次数。
动态规划填充:
  • 遍历 dna1dna2 的每个字符。
  • 如果字符相同,则 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
返回结果:

dp[m][n] 即为dna1dna2之间的最小变异次数。

测试用例
  • solution("AGT", "AGCT") 应返回 1
  • solution("", "ACGT") 应返回 4
  • solution("GCTAGCAT", "ACGT") 应返回 5

总结

通过动态规划,我们可以高效地计算两条DNA序列之间的最小变异次数,从而帮助小U分析不同物种之间的血缘关系。动态规划不仅解决了这个问题,还为我们提供了一种通用的解决问题的方法,适用于许多其他类似的问题。