AI刷题DNA序列编辑距离题解 | 豆包MarsCode AI刷题

84 阅读3分钟

DNA序列编辑距离

问题描述

小R正在研究DNA序列,他需要一个函数来计算将一个受损DNA序列(dna1)转换成一个未受损序列(dna2)所需的最少编辑步骤。编辑步骤包括:增加一个碱基、删除一个碱基或替换一个碱基。

测试样例

样例1:

输入:dna1 = "AGT",dna2 = "AGCT"
输出:1

样例2:

输入:dna1 = "AACCGGTT",dna2 = "AACCTTGG"
输出:4

样例3:

输入:dna1 = "ACGT",dna2 = "TGC"
输出:3

样例4:

输入:dna1 = "A",dna2 = "T"
输出:1

样例5:

输入:dna1 = "GGGG",dna2 = "TTTT"
输出:4

分析题目

你需要计算将一个受损DNA序列(dna1)转换成一个未受损序列(dna2)所需的最少编辑步骤。编辑步骤包括:

  1. 增加一个碱基
  2. 删除一个碱基
  3. 替换一个碱基
  4. 由于我们需要计算最小编辑距离,动态规划(Dynamic Programming, DP)是一个合适的选择。我们可以使用一个二维数组 dp 来存储中间结果。

算法设计

  1. 初始化

    • 如果 dna1 为空,那么将 dna1 转换为 dna2 需要 dna2.length() 次增加操作。
    • 如果 dna2 为空,那么将 dna1 转换为 dna2 需要 dna1.length() 次删除操作。
  2. 状态转移

    • 对于 dp[i][j],表示将 dna1 的前 i 个字符转换为 dna2 的前 j 个字符所需的最少编辑步骤。

    • 如果 dna1[i-1] == dna2[j-1],那么 dp[i][j] = dp[i-1][j-1],因为不需要任何编辑操作。

    • 否则,dp[i][j] 可以通过以下三种操作之一得到:

      • 替换:dp[i-1][j-1] + 1
      • 删除:dp[i-1][j] + 1
      • 增加:dp[i][j-1] + 1
    • 取这三种操作的最小值。

最终结果

dp[m][n] 即为将 dna1 转换为 dna2 所需的最少编辑步骤,其中 m 和 n 分别是 dna1 和 dna2 的长度

  • 时间复杂度O(m * n)
  • 空间复杂度O(m * n)

具体代码

``

   `public class Main {
public static int solution(String dna1, String dna2) {
   
   int m = dna1.length();
    int n = dna2.length();
    int[][] dp = new int[m + 1][n + 1];
    for (int i = 0; i <= m; i++)  {
        dp[i][0] = i;
    }
    for (int j = 0; j <= n; j++) {
        dp[0][j] = j;
    }
    for (int i = 1; i <= m; i++) {
        for (int j = 1; j <= n; j++) {
            if (dna1.charAt(i - 1) == dna2.charAt(j - 1)) {
                dp[i][j] = dp[i - 1][j - 1];
            } else {
                dp[i][j] = Math.min(dp[i - 1][j - 1] , Math.min(dp[i - 1][j] , dp[i][j - 1])) + 1;
            }
        }
    }
    return dp[m][n];
}
public static void main(String[] args) {
    System.out.println(solution("AGT", "AGCT")); 
    System.out.println(solution("AACCGGTT", "AACCTTGG")); 
    System.out.println(solution("ACGT", "TGC")); 
    System.out.println(solution("A", "T"));
    System.out.println(solution("GGGG", "TTTT")); 
}

}`

注意事项

好的,让我们总结一下在解决这个题目时需要注意的事项:

1. 边界条件

  • 空字符串

    • 如果 dna1 为空,那么将 dna1 转换为 dna2 需要 dna2.length() 次增加操作。
    • 如果 dna2 为空,那么将 dna1 转换为 dna2 需要 dna1.length() 次删除操作。

2. 动态规划数组的初始化

  • 初始化第一行和第一列

    • dp[i][0] 表示将 dna1 的前 i 个字符转换为空字符串所需的操作数,因此 dp[i][0] = i
    • dp[0][j] 表示将空字符串转换为 dna2 的前 j 个字符所需的操作数,因此 dp[0][j] = j

3. 状态转移

  • 字符相等

    • 如果 dna1[i-1] == dna2[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
      • 删除:dp[i-1][j] + 1
      • 增加:dp[i][j-1] + 1
    • 取这三种操作的最小值。

4. 最终结果

  • 返回值

    • dp[m][n] 即为将 dna1 转换为 dna2 所需的最少编辑步骤,其中 m 和 n 分别是 dna1 和 dna2 的长度。

5. 代码实现细节

  • 数组索引

    • 注意 dp 数组的索引是从 0 到 m 和 0 到 n,而字符串的索引是从 0 到 m-1 和 0 到 n-1
  • 字符比较

    • 使用 charAt 方法比较字符时,注意索引的偏移(i-1 和 j-1)。