古生物DNA序列血缘分析(算法解析)| 豆包MarsCode AI刷题

47 阅读5分钟

古生物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

问题理解

题目要求计算两条DNA序列之间的最小变异次数,这里的变异包括三种操作:插入、删除和替换。最小变异次数可以理解为将一条DNA序列转换成另一条DNA序列所需的最少编辑操作次数。

数据结构选择

为了计算最小变异次数,我们可以使用动态规划( DP)。动态规划是一种通过将问题分解为子问题并存储子问题的解来解决复杂问题的方法。

算法步骤

  1. 定义状态

    • 使用一个二维数组 dp,其中 dp[i][j] 表示将 dna1 的前 i 个字符转换成 dna2 的前 j 个字符所需的最小编辑距离。
  2. 初始化

    • dp[i][0] 表示将 dna1 的前 i 个字符转换成空字符串,需要 i 次删除操作。
    • dp[0][j] 表示将空字符串转换成 dna2 的前 j 个字符,需要 j 次插入操作。
  3. 状态转移

    • 如果 dna1 的第 i 个字符和 dna2 的第 j 个字符相同,则 dp[i][j] = dp[i-1][j-1],即不需要额外的编辑操作。

    • 如果不同,则需要考虑三种操作:

      • 插入dp[i][j-1] + 1
      • 删除dp[i-1][j] + 1
      • 替换dp[i-1][j-1] + 1
    • 取这三种操作的最小值作为 dp[i][j]

  4. 结果

    • 最终结果为 dp[m][n],其中 m 和 n 分别是 dna1 和 dna2 的长度。

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];  
  
        // 初始化dp数组的第一行和第一列  
        for (int i = 0; i <= m; i++) {  
            dp[i][0] = i; // 将dna1的前i个字符转换成空字符串,需要i次删除操作  
        }  
        for (int j = 0; j <= n; j++) {  
            dp[0][j] = j; // 将空字符串转换成dna2的前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 {  
                    int insert = dp[i][j - 1] + 1; // 插入操作  
                    int delete = dp[i - 1][j] + 1; // 删除操作  
                    int replace = dp[i - 1][j - 1] + 1; // 替换操作  
                    dp[i][j] = Math.min(Math.min(insert, delete), replace); // 取最小值  
                }  
            }  
        }  
  
        // 返回dna1转换成dna2所需的最小编辑距离  
        return dp[m][n];  
    }

    public static void main(String[] args) {
        // You can add more test cases here
        System.out.println(solution("AGT", "AGCT") == 1);
        System.out.println(solution("AACCGGTT","AACCTTGG") == 4);
        System.out.println(solution("A", "T") == 1);
    }
}

通过动态规划,我们可以有效地计算出两条DNA序列之间的最小变异次数。这个方法的时间复杂度为 O(m * n),其中 m 和 n 分别是两条DNA序列的长度。

知识点总结

  1. 动态规划(Dynamic Programming, DP)

    • 概念:动态规划是一种通过将问题分解为子问题并存储子问题的解来解决复杂问题的方法。它通常用于优化问题,如最短路径、最长公共子序列等。
    • 应用:在本题中,我们使用动态规划来计算两条DNA序列之间的最小变异次数。通过构建一个二维数组 dp,我们可以逐步计算出将一条序列转换成另一条序列所需的最少编辑操作次数。
  2. 编辑距离(Edit Distance)

    • 概念:编辑距离是指将一个字符串转换成另一个字符串所需的最少编辑操作次数。常见的编辑操作包括插入、删除和替换。
    • 应用:本题中,编辑距离被用来衡量两条DNA序列之间的相似度。通过计算编辑距离,我们可以判断两条序列之间的血缘关系。
  3. 二维数组

    • 概念:二维数组是一个表格形式的数组,其中每个元素可以通过两个索引(行和列)来访问。
    • 应用:本题中,我们使用二维数组 dp 来存储子问题的解。dp[i][j] 表示将 dna1 的前 i 个字符转换成 dna2 的前 j 个字符所需的最小编辑距离。
  4. 状态转移方程

    • 概念:状态转移方程描述了如何从一个状态转移到另一个状态。在动态规划中,状态转移方程是解决问题的关键。
    • 应用:本题中,状态转移方程描述了如何根据当前字符是否相等来更新 dp 数组。如果字符相等,则不需要额外的编辑操作;如果字符不相等,则需要考虑插入、删除和替换三种操作,并取最小值。
  5. 边界条件

    • 概念:边界条件是指在问题规模最小时的情况,通常用于初始化动态规划的状态。
    • 应用:在本题中,我们初始化了 dp 数组的第一行和第一列。dp[i][0] 表示将 dna1 的前 i 个字符转换成空字符串,需要 i 次删除操作;dp[0][j] 表示将空字符串转换成 dna2 的前 j 个字符,需要 j 次插入操作。