DNA序列转换问题 | 豆包MarsCode AI刷题

97 阅读5分钟

解题分析与总结:豆包MarsCode AI刷题

题目解析

今天我们来解答一道经典的动态规划问题——DNA序列转换问题。该问题描述了如何通过最少的编辑步骤将一个受损的DNA序列(dna1)转换为一个未受损的DNA序列(dna2)。每个编辑步骤可以是插入、删除或替换一个碱基。

问题描述:

  • 给定两个DNA序列 dna1dna2,我们需要计算将 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

思路分析

这道题目可以通过动态规划(DP)来解决。我们定义一个二维数组 dp,其中 dp[i][j] 表示将 dna1[0..i-1] 转换为 dna2[0..j-1] 的最少编辑距离。

  • 状态转移:

    • 插入操作: 如果我们需要将 dna2[j-1] 插入到 dna1 中,那么 dp[i][j] = dp[i][j-1] + 1
    • 删除操作: 如果我们需要从 dna1[i-1] 中删除一个碱基,那么 dp[i][j] = dp[i-1][j] + 1
    • 替换操作: 如果 dna1[i-1] != dna2[j-1],我们需要进行替换,dp[i][j] = dp[i-1][j-1] + 1;如果 dna1[i-1] == dna2[j-1],则不需要进行替换,dp[i][j] = dp[i-1][j-1]
  • 初始化:

    • dp[i][0] = i,表示将 dna1[0..i-1] 转换为空序列需要进行 i 次删除操作。
    • dp[0][j] = j,表示将空序列转换为 dna2[0..j-1] 需要进行 j 次插入操作。

代码实现

基于上述思路,我们可以写出如下的代码实现:

java
复制代码
public class Main {
    public static int solution(String dna1, String dna2) {
        int n = dna1.length();
        int m = dna2.length();
        int[][] dp = new int[n + 1][m + 1];

        // 初始化边界条件
        for (int i = 1; i <= n; i++) dp[i][0] = i;
        for (int j = 1; j <= m; j++) dp[0][j] = j;

        // 填充dp表
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= m; j++) {
                // 插入、删除、替换操作的状态转移
                dp[i][j] = Math.min(dp[i-1][j-1] + 1, Math.min(dp[i][j-1] + 1, dp[i-1][j] + 1));
                
                // 如果字符相同,则不需要替换
                if (dna1.charAt(i-1) == dna2.charAt(j-1)) {
                    dp[i][j] = Math.min(dp[i-1][j-1], dp[i][j]);
                }
            }
        }

        // 返回最终结果,即将dna1转换为dna2的最少编辑步骤
        return dp[n][m];
    }

    public static void main(String[] args) {
        System.out.println(solution("AGT", "AGCT") == 1);  // 1次操作
        System.out.println(solution("AACCGGTT", "AACCTTGG") == 4);  // 4次操作
        System.out.println(solution("ACGT", "TGC") == 3);  // 3次操作
        System.out.println(solution("A", "T") == 1);  // 1次操作
        System.out.println(solution("GGGG", "TTTT") == 4);  // 4次操作
    }
}

代码详解

  1. 初始化二维数组 dp:
    我们首先初始化了 dp 数组的边界:

    • dp[i][0] = i,表示将 dna1[0..i-1] 转换为空序列所需的操作次数是 i(即需要删除 i 次)。
    • dp[0][j] = j,表示将空序列转换为 dna2[0..j-1] 所需的操作次数是 j(即需要插入 j 次)。
  2. 动态规划递推:
    通过双重循环遍历 dp[i][j],对于每个位置,我们考虑:

    • 插入操作:dp[i][j-1] + 1
    • 删除操作:dp[i-1][j] + 1
    • 替换操作:dp[i-1][j-1] + 1
    • 如果当前字符相等,则不需要替换:dp[i-1][j-1]
  3. 最终结果:
    最终,dp[n][m] 就是将 dna1 转换为 dna2 所需的最少编辑步骤。

知识总结与学习建议

在这道题目中,我们主要用到了动态规划的思想,特别是在计算字符串之间的最小编辑距离时。通过构造一个二维的状态转移表,逐步解决问题。以下是几点总结:

  • 动态规划的状态定义与转移:
    动态规划的核心是定义状态和转移方程。此题中,我们通过 dp[i][j] 来表示 dna1[0..i-1]dna2[0..j-1] 的最小编辑距离,并根据插入、删除、替换操作的情况来填充 dp 表。
  • 从基础到进阶:
    动态规划问题往往从简单的子问题开始逐步解决。理解每一步的状态转移非常重要,要清楚每一步是如何从前一步推导过来的。

学习计划与方法

结合豆包MarsCode AI的刷题功能,可以采取以下学习策略:

  1. 分阶段刷题: 从简单的动态规划问题开始,逐步增加难度。例如,先从计算两个字符串的编辑距离开始,再逐步挑战更复杂的动态规划题目。
  2. 错题复习: 利用错题本功能,回顾自己在理解动态规划问题时的难点,分析错误原因,并加强相应的知识点。
  3. 结合参考资料: 在进行动态规划刷题时,可以结合其他学习资料,如算法书籍或视频教程,加深对状态转移方程的理解。

通过这种方式,可以在短时间内掌握动态规划的核心思想和应用技巧。

结语

这道DNA序列编辑问题不仅考察了我们对动态规划的掌握程度,还锻炼了我们如何通过状态转移方程来解决实际问题。通过不断的练习和总结,我们可以将动态规划运用自如,为解决更多复杂的算法问题打下坚实的基础。