81 古生物DNA序列血缘分析(最短编辑距离问题)| 豆包MarsCode AI 刷题

61 阅读4分钟

问题描述

DNA 是由 A、C、G、T 四种核苷酸组成,例如AAAGTCTGAC,假定自然环境下 DNA 发生异变的情况有:

  1. 基因缺失一个核苷酸
  2. 基因新增一个核苷酸
  3. 基因替换一个核苷酸

且发生概率相同。

古生物学家 Sam 得到了若干条相似 DNA 序列,Sam 认为一个 DNA 序列向另外一个 DNA 序列转变所需的最小异变情况数可以代表其物种血缘相近程度,异变情况数越少,血缘越相近,请帮助 Sam 实现获取两条 DNA 序列的最小异变情况数的算法。

输入格式

每个样例只有一行,两个 DNA 序列字符串以英文逗号“,”分割

输出格式

输出转变所需的最少情况数,类型是数字

输入样例

  • AGT,AGCT

输出样例

  • 1

数据范围

每个 DNA 序列不超过 100 个字符

解决思路:

这道题本质上实际是最短编辑距离类的问题。

最短编辑距离(Edit Distance)问题是计算将一个字符串转换为另一个字符串所需的最小操作数。允许的操作通常包括:

  1. 插入一个字符。
  2. 删除一个字符。
  3. 替换一个字符。

举个例子: 给定字符串 s1 = "kitten" 和 s2 = "sitting",我们需要计算将 kitten 转换为 sitting 的最小操作次数。

为什么说将一个字符串A转换为另一个字符串B只有三种操作呢?这是因为对一个字符串A的插入操作与对另一个字符串B的删除操作是等价的,同理,二者的替换操作也是等价的,所以我们只需要考虑这三种情况就可以了。 解决最短编辑距离问题的核心是使用动态规划。我们构建一个二维数组 dp,其中 dp[i][j] 表示将 s1 的前 i 个字符转换为 s2 的前 j 个字符所需的最小编辑距离。

状态转移

  1. 初始化
    • 当 s1 或 s2 为空时,编辑距离等于另一个字符串的长度。
    • dp[0][0] = 0(两个空字符串的距离为0)。
    • dp[i][0] = i(从 s1 的前 i 个字符到空字符串的距离为 i)。
    • dp[0][j] = j(从空字符串到 s2 的前 j 个字符的距离为 j)。
  2. 填充 dp 数组
    • 如果 s1[i-1] == s2[j-1],则不需要额外的操作:

dp[i][j]=dp[i−1][j−1]dp[i][j] = dp[i-1][j-1]dp[i][j]=dp[i−1][j−1]

    • 如果 s1[i-1] != s2[j-1],我们需要考虑三种操作:
      • 插入:将一个字符插入到 s1,使得 dp[i][j] = dp[i][j-1] + 1。
      • 删除:删除 s1 的一个字符,得到 dp[i][j] = dp[i-1][j] + 1。
      • 替换:替换 s1[i-1] 为 s2[j-1],得到 dp[i][j] = dp[i-1][j-1] + 1。
    • 综合这三者,取最小值:

dp[i][j]=min(dp[i1][j]+1,dp[i][j1]+1,dp[i1][j1]+1)dp[i][j]=min(dp[i1][j]+1,dp[i][j1]+1,dp[i1][j1]+1)dp[i][j]=min(dp[i1][j]+1,dp[i][j1]+1,dp[i1][j1]+1)dp[i][j]=min⁡(dp[i−1][j]+1,dp[i][j−1]+1,dp[i−1][j−1]+1)dp[i][j] = \min(dp[i-1][j] + 1, dp[i][j-1] + 1, dp[i-1][j-1] + 1)dp[i][j]=min(dp[i−1][j]+1,dp[i][j−1]+1,dp[i−1][j−1]+1)

 

复杂度分析

  • 时间复杂度:O(m * n),其中 m 和 n 分别是两个字符串的长度。我们需要填充一个大小为 (m+1) x (n+1) 的动态规划表。
  • 空间复杂度:O(m * n),存储动态规划表。如果需要优化空间复杂度,可以只使用一维数组来保存当前和上一行的结果。

在最短编辑距离的动态规划解决方案中,每个状态 dp[i][j] 只依赖于当前行 i 和前一行 i-1 的状态。因此,我们并不需要保存整个二维数组,而只需要保存当前行和前一行的结果。

  1. 使用两行一维数组
    • 我们可以使用两个一维数组 prev 和 curr,分别存储 dp[i-1] 和 dp[i] 的值。
    • 对于每一行的计算,我们只需要使用当前行的结果和前一行的结果即可。
  2. 更新规则
    • 在计算 dp[i][j] 时,使用 prev[j] 来表示 dp[i-1][j],而 curr[j-1] 来表示 dp[i][j-1]。
    • 当我们完成一行的计算后,将 curr 数组的值复制到 prev 数组,以便在下一行的计算中使用。