问题描述
给定一段受损的 DNA 碱基序列 dna1,在每次只操作一个碱基的情况下,将其以最少的操作步骤将其还原到未受损的 DNA 碱基序列 dna2。
只可以对 DNA 碱基序列中的一个碱基进行三种操作:
- 增加一个碱基
- 去除一个碱基
- 替换一个碱基
输入描述:
输入两段 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 个字符所需要的最小操作数。
步骤如下:
-
定义状态:
- 我们用二维数组
f[i][j]来表示将dna1的前i个字符转换为dna2的前j个字符所需的最少操作次数。
- 我们用二维数组
-
初始化:
f[i][0] = i:这意味着如果要把dna1的前i个字符变为空串,那最少需要i次删除操作。f[0][j] = j:同理,如果要把空串变成dna2的前j个字符,那最少需要j次增加操作。
-
状态转移方程:
-
我们要考虑
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]。 -
-
返回结果:
- 最后我们需要返回
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];
}