【动态规划】[No.26]DNA序列编辑距离 | 豆包MarsCode AI 刷题

54 阅读4分钟

题干部分:

问题描述:

给定一段受损的 DNA 碱基序列 dna1,在每次只操作一个碱基的情况下,将其以最少的操作步骤将其还原到未受损的 DNA 碱基序列 dna2

只可以对 DNA 碱基序列中的一个碱基进行三种操作:

  1. 增加一个碱基
  2. 去除一个碱基
  3. 替换一个碱基

输入描述:

输入两段 DNA 碱基序列,每段分一行输入

第一行为第一段受损的 DNA 碱基序列 dna1

第二行为第二段未受损的 DNA 碱基序列 dna2

输出描述:

最小操作步骤数

备注:

0 <= dna1.lengthdna2.length <= 500

dna1 和 dna2 由大写英文字母 A、G、C、T 组成

问题分析:

这是一道典型的动态规划问题。我们可以按照动态规划问题的求解步骤,通过分析每个小步骤的状态变化对该题进行求解。

  1. 定义状态:我们可以使用一个二维数组 dp,其中 dp[i][j] 表示将 dna1 的前 i 个字符转换为 dna2 的前 j 个字符所需的最小操作步骤数。

  2. 初始化

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

    • 如果 dna1[i-1] == dna2[j-1],则 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]

  4. 最终结果dp[dna1.length()][dna2.length()] 即为将 dna1 转换为 dna2 所需的最小操作步骤数。

动态规划流程分析

1. 定义状态

我们使用一个二维数组 dp,其中 dp[i][j] 表示将 dna1 的前 i 个字符转换为 dna2 的前 j 个字符所需的最小操作步骤数。

2. 初始化

  • 初始化边界条件

    • dp[i][0] 表示将 dna1 的前 i 个字符转换为空字符串,显然需要 i 次删除操作。
    • dp[0][j] 表示将空字符串转换为 dna2 的前 j 个字符,显然需要 j 次插入操作。


for (int i = 0; i <= m; ++i) 

{

    dp[i][0] = i;

}

for (int j = 0; j <= n; ++j) 

{

    dp[0][j] = j;

}

3. 状态转移

  • 填充 dp 数组

    • 如果 dna1[i-1] == dna2[j-1],则 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]


for (int i = 1; i <= m; ++i) 

{

    for (int j = 1; j <= n; 

    ++j) {

        if (dna1[i - 1] == 

        dna2[j - 1]) {

            dp[i][j] = dp[i 

            - 1][j - 1];

        } else {

            dp[i][j] = min

            ({dp[i - 1][j], 

            dp[i][j - 1], dp

            [i - 1][j - 1]}) 

            + 1;

        }

    }

}

4. 最终结果

dp[m][n] 即为将 dna1 转换为 dna2 所需的最小操作步骤数。

return dp[m][n];

代码实现

#include <iostream>

#include <string>

#include <vector>

#include <algorithm>

using namespace std;

int solution(string dna1, 

string dna2) {

    int m = dna1.length();

    int n = dna2.length();

    

    // 创建一个二维数组 dp

    vector<vector<int>> dp(m 

    + 1vector<int>(n + 1, 

    0));

    

    // 初始化 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[i - 1] 

            == dna2[j - 1]) {

                dp[i][j] = dp

                [i - 1][j - 

                1];

            } else {

                dp[i][j] = 

                min({dp[i - 

                1][j], dp[i]

                [j - 1], dp

                [i - 1][j - 

                1]}) + 1;

            }

        }

    }

    

    // 返回最终结果

    return dp[m][n];

}

int main() {

    // 你可以添加更多测试用例

    cout << (solution

    ("AGCTTAGC""AGCTAGCT") 

    == 2) << endl;

    cout << (solution

    ("AGCCGAGC""GCTAGCT") 

    == 4) << endl;

    return 0;

}

关键点总结

  • 定义状态dp[i][j] 表示将 dna1 的前 i 个字符转换为 dna2 的前 j 个字符所需的最小操作步骤数。
  • 初始化:处理边界情况,即空字符串转换的情况。
  • 状态转移:根据当前字符是否相等,选择最小的操作数。
  • 最终结果dp[m][n] 即为最终答案。