题目解析: 16. DNA序列还原 | 豆包MarsCode AI刷题

83 阅读4分钟

问题描述

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

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

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

输入描述:

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

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

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

输出描述:

最小操作步骤数

备注:

0 <= dna1.length, dna2.length <= 500

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

示例 1

输入

AGCTTAGC

AGCTAGCT

输出

2

说明

AGCTTAGC -> AGCTAGC(删除 T)

AGCTAGC -> AGCTAGCT(增加 T)

示例 2

输入

AGCCGAGC

GCTAGCT

输出

4

说明

AGCCGAGC -> GCCGAGC(删除 A)

GCCGAGC -> GCTGAGC(将 C 替换为 T)

GCTGAGC -> GCTAGC(删除 G)

GCTAGC -> GCTAGCT(增加 T)

难点在哪里?

  • 多种操作:我们可以对DNA序列中的每个碱基进行三种操作——增加、删除、替换。这些操作意味着每个位置的改变都会带来不同的结果,因此如何选择正确的操作是一个挑战。
  • 复杂度:在解决这个问题时,需要找出所有可能的操作组合,从而获得最小的操作步骤数。对每一种可能情况的讨论会让问题变得非常复杂,特别是序列长度比较大的时候(如500个字符)。
  • 动态规划的理解:本题最关键的部分是如何利用动态规划的方法去表示解决方案。这对于初学者来说可能比较难理解,因为它涉及到状态的定义和递推公式的推导。

解题思路

这个问题可以使用动态规划(DP)来解决,通过建立一个二维DP数组 f[i][j],其中 f[i][j] 表示将 dna1 的前 i 个字符变为 dna2 的前 j 个字符所需要的最小操作数。

步骤如下

  1. 定义状态

    • 我们用二维数组 f[i][j] 来表示将 dna1 的前 i 个字符转换为 dna2 的前 j 个字符所需的最少操作次数。
  2. 初始化

    • f[i][0] = i:这意味着如果要把 dna1 的前 i 个字符变为空串,那最少需要 i 次删除操作。
    • f[0][j] = j:同理,如果要把空串变成 dna2 的前 j 个字符,那最少需要 j 次增加操作。
  3. 状态转移方程

    • 我们要考虑 f[i][j] 的情况,这里 i > 0 且 j > 0

      • 如果 dna1[i] == dna2[j]:说明两个字符相同,不需要额外的操作,那么 f[i][j] = f[i-1][j-1]

      • 如果 dna1[i] != dna2[j]:需要进行一次替换、增加或删除操作:

        • 替换:f[i-1][j-1] + 1
        • 增加:f[i][j-1] + 1,即在 dna1 末尾增加一个和 dna2[j] 相同的字符。
        • 删除:f[i-1][j] + 1,即删除 dna1 中的一个字符。

    最后我们取最小值来更新 f[i][j],即:

    f[i][j]=min⁡(f[i−1][j]+1,f[i][j−1]+1,f[i−1][j−1]+flag)f[i][j]=min(f[i−1][j]+1,f[i][j−1]+1,f[i−1][j−1]+flag)

    其中 flag 为0或1,取决于 dna1[i] 是否等于 dna2[j]

  4. 返回结果

    • 最后我们需要返回 f[lena][lenb],也就是将整个 dna1 转换为 dna2 所需要的最少操作次数。

举个例子来说明: 假如我们有 dna1 = "AGCTTAGC" 和 dna2 = "AGCTAGCT"

  • 初始状态下,f 矩阵会根据初始条件进行填充,表示删除或增加字符的次数。
  • 然后通过递推公式逐个填充 f[i][j],不断比较每个字符,选择最小的操作数。

通过动态规划的方式,我们可以避免暴力枚举所有可能的操作组合,极大地提高了效率。

int solution(std::string dna1, std::string dna2) {
    int lena = dna1.length(), lenb = dna2.length();
    int f[N][N];
    for(int i = 1; i <= lena; i ++)
        for(int j = 1; j <= lenb; j ++)
            f[i][j] = 2e9;
    for(int i = 1; i <= lena; i ++){
        f[i][0] = i;
    }
    for(int i = 1; i <= lenb; i ++){
        f[0][i] = i;
    }
    
    for(int i = 1; i <= lena; i ++)
        for(int j = 1; j <= lenb; j ++){
            bool flag = 1;
            if(dna1[i] == dna2[j])
                flag = 0;
            
            f[i][j] = std::min(f[i - 1][j] + 1, std::min(f[i - 1][j - 1] + flag, f[i][j - 1] + 1));
    }
    

    return f[lena][lenb];
}