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

67 阅读3分钟

题目

问题描述

小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

解题思路

思想:动态规划

写在前面:对给定数组的一个调整,在dna1dna2的第0个位置插入一个占位符,为了使后面dp数组的含义更符合我自己的习惯与理解,因此dna1[1,n]dna2[1,m]是有效的字符。

状态转移

dp[i][j]表示dna1[1,i]通过三种操作变为dna2[1,j]的最小次数。

分为以下情况:

  • dna1[i] == dna2[j]时,不操作,只要延续前面的状态即可,所以有dp[i][j] = dp[i-1][j-1]

  • dna1[i] != dna2[j]时,需要操作,在以下三种操作中取最小值:

    • dna1中删除字符dna1[i]dp[i][j] = dp[i-1][j] + 1
    • dna1中增添字符dna2[j] :等价于在dna2中删除字符dna2[j]dp[i][j] = dp[i][j-1] + 1
    • dna1中修改字符dna1[i] :等价于要让dna1[i] == dna2[j]dp[i][j] = dp[i-1][j-1] + 1

由于dp[i][j]的状态是由dp[i-1][j-1], dp[i-1][j], dp[i][j-1]得到,因此遍历顺序为:从左到右、从上到下

初始化

  • 显然,当两个dna都为空的时候,不需要任何操作,因此有dp[0][0] = 0
  • dp[i][0]表示将一个长度为idna删空的操作数,因此有dp[i][0] = i
  • dp[0][j]表示将一个空的dna增添字符成为一个长度为jdna的操作数,因此有dp[0][j] = j

Code

 #include <iostream>
 #include <string>
 #include <vector>
 
 int solution(std::string dna1, std::string dna2) {
     int len1 = dna1.size(), len2 = dna2.size();
     dna1 = "." + dna1;
     dna2 = "." + dna2;
     std::vector<std::vector<int>> dp(len1+1, std::vector<int>(len2+1, 0));
     for (int i = 1; i <= len1; i++) dp[i][0] = i;
     for (int j = 1; j <= len2; j++) dp[0][j] = j;
 
     for (int i = 1; i <= len1; i++) {
         for (int j = 1; j <= len2; j++) {
             if (dna1[i] == dna2[j]) dp[i][j] = dp[i-1][j-1];
             else dp[i][j] = std::min(dp[i-1][j] + 1, std::min(dp[i][j-1] + 1, dp[i-1][j-1] + 1));
         }
     }
 
     return dp[len1][len2];
 }
 
 int main() {
     //  You can add more test cases here
     std::cout << (solution("AGCTTAGC", "AGCTAGCT") == 2) << std::endl;
     std::cout << (solution("AGCCGAGC", "GCTAGCT") == 4) << std::endl;
     return 0;
 }

复杂度分析

时间复杂度分析

  • 初始化 dp 数组时,我们需要将 dp[i][0] 设置为 idp[0][j] 设置为 j,这两个操作的时间复杂度是 O(m + n),其中 mn 分别是 dna1dna2 的长度。
  • 然后,遍历 dp 数组的每个元素并计算 dp[i][j],这一步的时间复杂度是 O(m * n),因为我们需要填充 m * n 个位置。

因此,整体的时间复杂度为: O(m * n) ,其中 mn 分别是 dna1dna2 的长度。

空间复杂度分析

空间复杂度主要由 dp 数组的大小决定。我们使用了一个二维数组 dp,其大小为 (m + 1) * (n + 1),所以空间复杂度为: O(m * n)

总结

  • 时间复杂度O(m * n),其中 mdna1 的长度,ndna2 的长度。
  • 空间复杂度O(m * n),用于存储 dp 数组。