题干部分:
问题描述:
给定一段受损的 DNA 碱基序列 dna1,在每次只操作一个碱基的情况下,将其以最少的操作步骤将其还原到未受损的 DNA 碱基序列 dna2。
只可以对 DNA 碱基序列中的一个碱基进行三种操作:
- 增加一个碱基
- 去除一个碱基
- 替换一个碱基
输入描述:
输入两段 DNA 碱基序列,每段分一行输入
第一行为第一段受损的 DNA 碱基序列 dna1
第二行为第二段未受损的 DNA 碱基序列 dna2
输出描述:
最小操作步骤数
备注:
0 <= dna1.length, dna2.length <= 500
dna1 和 dna2 由大写英文字母 A、G、C、T 组成
问题分析:
这是一道典型的动态规划问题。我们可以按照动态规划问题的求解步骤,通过分析每个小步骤的状态变化对该题进行求解。
-
定义状态:我们可以使用一个二维数组
dp,其中dp[i][j]表示将dna1的前i个字符转换为dna2的前j个字符所需的最小操作步骤数。 -
初始化:
dp[0][j]表示将空字符串转换为dna2的前j个字符,显然需要j次插入操作。dp[i][0]表示将dna1的前i个字符转换为空字符串,显然需要i次删除操作。
-
状态转移:
-
如果
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]。
-
-
最终结果:
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
+ 1, vector<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]即为最终答案。