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

40 阅读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

解题思路

本题的目标是计算编辑距离,我们可以使用动态规划的方法来实现。

  1. 创建动态规划数组:使用二维数组dp来存储编辑距离,dp[i][j]表示将dna1的前i个字符转换为dna2的前j个字符所需的最小编辑步骤。

  2. 初始化边界条件

    • 如果dna1长度为i,则删除i个字符的操作需要i步。
    • 如果dna2长度为j,则插入j个字符的操作需要j步。
  3. 状态转移

    • 如果当前字符相同,则dp[i][j]等于dp[i-1][j-1]
    • 如果不同,考虑三种操作(删除、插入、替换),选择其中的最小值。
  4. 返回结果:最终返回dp[m][n],即完整序列的编辑距离。

代码实现与详解

以下是完整的Java实现代码,计算dna1dna2的编辑距离:

java
复制代码
public class Main {
    public static int minDistance(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;
        }

        // 填充 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(Math.min(dp[i - 1][j] + 1, 
                                                   dp[i][j - 1] + 1), 
                                                   dp[i - 1][j - 1] + 1);
                }
            }
        }

        return dp[m][n];
    }

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

代码详解

  1. 动态规划数组dp用来记录转换的最小步数。
  2. 边界初始化:确保在转换到空字符串时的编辑步数被正确记录。
  3. 状态转移逻辑:通过判断字符相同或不同,来更新dp数组的值。
  4. 返回编辑距离:最终返回的结果是完成整个转换所需的最小步数。

知识总结

  • 动态规划:理解状态转移的逻辑是关键,特别是在处理复杂问题时。
  • 边界条件:初始条件的设定影响整个动态规划的结果,必须仔细处理。
  • 性能优化:在进行字符串处理时,使用合适的数据结构和算法可以有效提升程序的性能。

算法复杂度

  • 时间复杂度

  1. 初始化边界条件

    • 对于长度为 mdna1 和长度为 ndna2,我们需要初始化 dp 数组的第一行和第一列,因此这部分的时间复杂度为 O(m + n)。
  2. 动态规划数组填充

    • 在双重循环中,外层循环遍历 dna1 的每个字符(m次),内层循环遍历 dna2 的每个字符(n次)。对于每一对字符,我们都在 O(1) 的时间内进行比较和更新 dp 数组。因此,这部分的时间复杂度为 O(m * n)。

综上,整个算法的时间复杂度为: O(m⋅n)

  • 空间复杂度

  1. 我们使用了一个大小为 (m + 1) x (n + 1) 的二维数组 dp 来存储编辑距离,因此空间复杂度为: O(m⋅n)