动态规划Day-1 DNA序列编辑距离 | 豆包MarsCode AI刷题

54 阅读4分钟

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

要解决这个问题,我们可以使用 动态规划(Dynamic Programming) 来计算两个DNA序列之间的编辑距离。编辑距离是指将一个字符串转换为另一个字符串所需的最少编辑操作次数,编辑操作包括插入、删除和替换字符。

解题思路

  1. 定义问题
    • 我们需要计算两个字符串 dna1 和 dna2 之间的编辑距离。
  2. 定义动态规划表
    • 创建一个二维数组 dp,其中 dp[i][j] 表示将 dna1 的前 i 个字符转换为 dna2 的前 j 个字符所需的最少编辑操作次数。
  3. 初始化
    • dp[0][j]  表示将空字符串转换为 dna2 的前 j 个字符,需要 j 次插入操作。
    • dp[i][0]  表示将 dna1 的前 i 个字符转换为空字符串,需要 i 次删除操作。
  4. 状态转移方程
  • 如果 dna1[i-1](dna1的第i个字符) == dna2[j-1](dna2的第j个字符) ,则 dp[i][j] = dp[i-1][j-1] ,因为不需要任何编辑操作。
  • 否则,dp[i][j]  可以通过以下三种操作中的最小值得到:
    • 插入:dp[i][j-1] + 1
    • 删除:dp[i-1][j] + 1
    • 替换:dp[i-1][j-1] + 1

状态转移方程的推导(重点)

状态转移方程是动态规划的核心,它描述了如何从子问题的解推导出更大问题的解。对于编辑距离问题,我们需要考虑三种基本的编辑操作:插入、删除和替换。

状态转移

对于 dp[i][j] ,我们有以下几种情况:

  1. 字符相等

    • 如果 dna1[i-1] == dna2[j-1] ,则不需要任何编辑操作。因此,dp[i][j] = dp[i-1][j-1]
  1. 字符不相等

如果 dna1[i-1] != dna2[j-1] ,则需要进行编辑操作。我们有三种选择:

  • 插入:在 dna1 的第 i 个位置 dna1[i-1] )后面 插入 dna2[j-1] ,这样问题就转化为了要使 dna1 的前 i 个字符变成 dna2 的前 j-1 个字符,再加上插入操作的代价 1

    • dp[i][j] = dp[i][j-1] + 1
  • 删除:删除 dna1 的第 i 个字符 dna1[i-1] ,这样问题就转化为了要使 dna1 的前 i-1 个字符变成 dna2 的前 j 个字符,再加上删除操作的代价 1

    • dp[i][j] = dp[i-1][j] + 1
  • 替换:将 dna1 的第 i 个字符 dna1[i-1] 替换为 dna2 的第 j 个字符,这样问题就转化为了要使 dna1 的前 i-1 个字符变成 dna2 的前 j-1 个字符,再加上替换操作的代价 1

    • dp[i][j] = dp[i-1][j-1] + 1

因此,

  • 当 dna1[i-1] != dna2[j-1]  时,dp[i][j]  应该是这三种操作中的最小值:

    dp[i][j] = min( dp[i-1][j-1] , dp[i-1][j] , dp[i][j-1] ) + 1

  • 当 dna1[i-1] == dna2[j-1]  时,

    dp[i][j] = dp[i-1][j-1]

代码如下:

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

    }

}