古生物DNA序列血缘分析| 豆包MarsCode AI 刷题

84 阅读6分钟

题目解析:古生物DNA序列血缘分析

小U是一位古生物学家,正在研究不同物种之间的血缘关系。为了分析两种古生物的血缘远近,她需要比较它们的DNA序列。DNA由四种核苷酸A、C、G、T组成,并且可能通过三种方式发生变异:添加一个核苷酸、删除一个核苷酸或替换一个核苷酸。小U认为两条DNA序列之间的最小变异次数可以反映它们之间的血缘关系:变异次数越少,血缘关系越近。

你的任务是编写一个算法,帮助小U计算两条DNA序列之间所需的最小变异次数。

  • dna1: 第一条DNA序列。
  • dna2: 第二条DNA序列。

测试样例

样例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 这个问题本质上是一个 编辑距离(Edit Distance)Levenshtein 距离 问题。编辑距离是用来衡量两条字符串之间的差异的,具体来说,它是将一个字符串转换成另一个字符串所需要的最小操作数,允许的操作包括:插入、删除和替换一个字符。

题目分析

小U需要比较两条DNA序列之间的最小变异次数。对于每个字符,可能有以下几种操作:

  • 插入:在一个序列中插入一个字符。
  • 删除:从一个序列中删除一个字符。
  • 替换:用一个字符替换掉另一个字符。

我们需要求解的是从 dna1dna2 的最小变异次数,或称为编辑距离。

解题思路

编辑距离问题通常使用 动态规划(Dynamic Programming, DP) 来求解。动态规划的思想是通过构造一个二维数组,逐步计算出从 dna1 的前缀到 dna2 的前缀的最小编辑距离。

步骤如下:

  1. 状态定义

    • 设 dp[i][j] 表示将 dna1 的前 i 个字符转化为 dna2 的前 j 个字符所需的最小变异次数。
  2. 初始化

    • dp[0][0] = 0,即两个空字符串之间的编辑距离为 0。
    • 对于任何 idp[i][0] = i,表示将 dna1 的前 i 个字符转换为空字符串需要 i 次删除操作。
    • 对于任何 jdp[0][j] = j,表示将空字符串转换为 dna2 的前 j 个字符需要 j 次插入操作。
  3. 状态转移

    • 如果 dna1[i-1] == dna2[j-1],则 dp[i][j] = dp[i-1][j-1],表示当前字符相同,不需要任何操作。

    • 如果 dna1[i-1] != dna2[j-1],则我们需要考虑三种操作:

      • 插入:在 dna1 中插入一个字符,dp[i][j] = dp[i][j-1] + 1
      • 删除:从 dna1 中删除一个字符,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][j] 的值是以上三种操作中的最小值。

  4. 最终结果

    • 计算完成后,dp[dna1.length()][dna2.length()] 就是从 dna1 到 dna2 的最小变异次数。

代码实现

javaCopy Code
public class Main {
    public static int solution(String dna1, String dna2) {
        int m = dna1.length();
        int n = dna2.length();
        
        // 初始化dp数组,dp[i][j]表示将dna1前i个字符转换为dna2前j个字符所需的最小操作数
        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);
                    // 1. 删除一个字符:dp[i - 1][j] + 1
                    // 2. 插入一个字符:dp[i][j - 1] + 1
                    // 3. 替换一个字符:dp[i - 1][j - 1] + 1
                }
            }
        }
        
        // 返回dp[m][n],即从dna1到dna2的最小编辑距离
        return dp[m][n];
    }

    public static void main(String[] args) {
        // 测试用例
        System.out.println(solution("AGT", "AGCT") == 1);  // 应输出 1
        System.out.println(solution("", "ACGT") == 4);     // 应输出 4
        System.out.println(solution("GCTAGCAT", "ACGT") == 5); // 应输出 5
    }
}

代码解释

  1. 初始化 dp 数组

    • dp[i][j] 表示将 dna1 的前 i 个字符转换为 dna2 的前 j 个字符所需的最小操作数。我们需要一个 (m+1) x (n+1) 的二维数组来保存这些值,其中 m 是 dna1 的长度,n 是 dna2 的长度。
    • dp[i][0] = i 表示将 dna1 的前 i 个字符变为空字符串的操作次数(即删除 i 次)。
    • dp[0][j] = j 表示将空字符串变为 dna2 的前 j 个字符的操作次数(即插入 j 次)。
  2. 填充 dp 数组

    • 我们逐步计算 dp[i][j],考虑三种操作:插入、删除和替换,取它们中的最小值。
  3. 返回结果

    • 最终 dp[m][n] 即为从 dna1 到 dna2 的最小编辑距离。

时间复杂度

  • 时间复杂度O(m * n),其中 m 和 n 分别是两个字符串的长度。我们需要遍历整个 dp 数组,填充每个位置。
  • 空间复杂度O(m * n),需要一个大小为 (m+1) x (n+1) 的二维数组来存储中间结果。

示例分析

示例 1:

输入:

javaCopy Code
solution("AGT", "AGCT")
  • dna1 = "AGT"dna2 = "AGCT"。两个字符串的长度分别为 3 和 4。
  • 删除 'T',插入 'C',需要 1 次操作,返回值是 1

示例 2:

输入:

javaCopy Code
solution("", "ACGT")
  • dna1 是空字符串,dna2 = "ACGT"。需要插入 4 个字符 'A''C''G''T',返回值是 4

示例 3:

输入:

javaCopy Code
solution("GCTAGCAT", "ACGT")
  • dna1 = "GCTAGCAT"dna2 = "ACGT",通过替换、删除操作需要 5 次操作,返回值是 5

总结

该问题通过动态规划方法计算编辑距离,通过设计合理的状态转移方程和初始化边界条件,能够高效地解决问题。时间复杂度和空间复杂度都比较符合要求,适用于实际的DNA序列比对问题。

心得:

使用MarsCode AI编写代码让我体验到了编程的便利与高效。AI能够快速生成代码示例,帮助我理解不同编程概念。通过交互式的反馈,我能迅速调整思路,解决问题。同时,MarsCode AI提供的建议让我了解到更多最佳实践,提升了我的编码水平。这种工具不仅节省了时间,还激发了我的创造力,尤其是在处理复杂问题时,AI的支持显得尤为重要。总的来说,MarsCode AI是编程学习和实践中的得力助手。