青训营X豆包MarsCode 技术训练营第(一)课 | 豆包MarsCode AI 刷题

34 阅读5分钟

给大家分享一下我最近刷的几道题的解析,还有我对 AI 助手的使用心得,这是第一次给大家分享的刷题解析和经验分享,这个系列预计有六期,今天给大家带来第一期的分享。

今天的第一题 —— 计算 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

解决方案
我们可以通过动态规划的方法来解决这个问题,具体步骤如下:

  1. 创建二维数组 dp:

    • 首先确定受损 DNA 序列 dna1 的长度 m 和未受损序列 dna2 的长度 n。
    • 创建一个二维数组 dp,其大小为 [m + 1][n + 1],用于存储中间计算结果。
  2. 初始化 dp 数组:

    • 对于 dp 数组的第一行(i = 0),即当受损序列为空时,要变成未受损序列需要的操作次数就是未受损序列的长度,所以 dp [0][j] = j(j 从 0 到 n)。
    • 同理,对于 dp 数组的第一列(j = 0),即当未受损序列为空时,要将受损序列变成空需要的操作次数就是受损序列的长度,所以 dp [i][0] = i(i 从 0 到 m)。
  3. 填充 dp 数组:

    • 通过两层循环遍历 dp 数组(i 从 1 到 m,j 从 1 到 n)。

    • 当 dna1 的第 i - 1 个字符与 dna2 的第 j - 1 个字符相同时,说明这两个位置不需要进行编辑操作,此时 dp [i][j] 的值就等于 dp [i - 1][j - 1]。

    • 当这两个字符不同时,就需要考虑三种编辑操作:

      • 替换操作:将 dp [i - 1][j - 1] 的值加 1,表示把 dna1 的第 i - 1 个字符替换成 dna2 的第 j - 1 个字符所需的最少步骤,即 dp [i][j] = dp [i - 1][j - 1] + 1。
      • 删除操作:将 dp [i - 1][j] 的值加 1,表示删除 dna1 的第 i - 1 个字符所需的最少步骤,即 dp [i][j] = dp [i - 1][j] + 1。
      • 插入操作:将 dp [i][j - 1] 的值加 1,表示在 dna1 的第 i - 1 个字符前面插入一个字符使其与 dna2 的第 j - 1 个字符匹配所需的最少步骤,即 dp [i][j] = dp [i][j - 1] + 1。
      • 然后取这三种操作中的最小值作为 dp [i][j] 的值,即 dp [i][j] = Math.min (dp [i - 1][j - 1] + 1, Math.min (dp [i - 1][j] + 1, dp [i][j - 1] + 1))。
  4. 返回最终结果:

    • 最后,dp [m][n] 的值就是将受损 DNA 序列 dna1 转换成未受损序列 dna2 所需的最少编辑步骤,返回 dp [m][n] 即可。

public class Main {
    public static int solution(String dna1, String dna2) {
        int m = dna1.length();
        int n = dna2.length();

        // 创建一个二维数组dp
        int[][] dp = new int[m + 1][n + 1];

        // 初始化dp数组
        for (int i = 0; i <= m; i++) {
            dp[i][0] = i;
        }
        for (int j = 0; j <= n; j++) {
            dp[0][j] = j;
        }

        // 填充dp数组
        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] + 1, // 替换
                                        Math.min(dp[i - 1][j] + 1, // 删除
                                                 dp[i][j - 1] + 1)); // 插入
                }
            }
        }

        // 返回最终结果
        return dp[m][n];
    }
    public static void main(String[] args) {
    
        System.out.println(solution("AGCTTAGC", "AGCTAGCT") == 2);
        System.out.println(solution("AGCCGAGC", "GCTAGCT") == 4);
    }
}

代码功能概述
这段代码主要实现了计算将一个受损 DNA 序列转换为未受损序列所需的最少编辑步骤的功能。通过动态规划的思想,利用二维数组 dp 来记录中间计算结果,逐步填充数组以找到最优解,即最少的编辑步骤数量。

代码细节

类定义与函数布局:

  • Main是一个包含solution方法的公开类。solution方法接收两个字符串dna1dna2作为参数,分别代表受损 DNA 序列和未受损 DNA 序列,并返回将dna1转换成dna2所需的最少编辑步骤数。

字符处理逻辑:

  • 在填充dp数组时,通过比较dna1dna2对应位置的字符是否相同来决定dp[i][j]的值。当字符相同时,直接继承前一个状态的值;当字符不同时,综合考虑替换、删除和插入三种编辑操作的情况,选取其中最少步骤的情况作为当前状态的值。

结果拼接与返回:

  • 不需要进行额外的结果拼接操作,最终直接返回dp[m][n]作为计算得到的最少编辑步骤数。

核心逻辑
这段代码的核心在于动态规划的应用。通过建立二维数组dp并合理地初始化和填充它,根据字符是否相同来动态地选择不同的计算方式,从而有效地找出将受损 DNA 序列转换为未受损序列的最少编辑步骤。整个过程是基于对不同编辑操作的分析和比较,以达到最优解的目的。由于需要遍历两个序列的每个字符来填充dp数组,所以算法的时间复杂度为,其中是受损 DNA 序列的长度,是未受损 DNA 序列的长度。

总结
这段代码利用动态规划的方法清晰、高效地解决了计算 DNA 序列转换最少编辑步骤的问题。通过对不同编辑操作的细致分析和合理利用二维数组记录中间结果,使得代码逻辑易于理解且能准确地得到最优解。这不仅加深了我们对动态规划算法的理解,也让我们看到了如何在处理类似的序列转换问题中应用这种有效的算法策略。