题意简述
小R正在研究DNA序列,他需要一个函数来计算将一个受损DNA序列(dna1)转换成一个未受损序列(dna2)所需的最少编辑步骤。编辑步骤包括:增加一个碱基、删除一个碱基或替换一个碱基。
样例:
输入:dna1 = "AACCGGTT",dna2 = "AACCTTGG"
输出:4
思路分析
很容易联想到,这个题与求解最长公共子序列(LCS)问题有着相似的特点,在解决这个问题时,可以考虑用动态规划去解题。因为我们需要通过对比两个序列中字符的相似性和差异性来逐步推出答案。
状态定义
我们定义一个状态 dp[i][j],表示将 dna1 的前 i 个字符转换为 dna2 的前 j 个字符所需的最小编辑步骤。为了解决这个问题,我们需要确定初始状态。
-
初始状态:
dp[0][j]:表示将空字符串转换成dna2的前j个字符。为了实现这一点,我们需要进行j次插入操作,因此dp[0][j] = j。dp[i][0]:表示将dna1的前i个字符转换为空字符串。为了实现这一点,我们需要进行i次删除操作,因此dp[i][0] = i。
状态转移方程
接下来,我们需要计算 dp[i][j] 的值,这个计算依赖于前面的状态。我们可以考虑以下几种情况:
-
字符相同:
- 如果
dna1[i-1] == dna2[j-1],这意味着我们不需要进行额外的编辑步骤。此时,我们可以直接从dp[i-1][j-1]转移过来,因此:
- 如果
-
字符不同:
-
如果
dna1[i-1]与dna2[j-1]不同,我们需要考虑三种可能的编辑操作:- 插入操作:我们可以将
dna1的前i个字符转变为dna2的前j-1个字符,然后在末尾插入一个字符,因此: - 删除操作:我们可以将
dna1的前i-1个字符转变为dna2的前j个字符,然后删除一个字符,因此: - 替换操作:我们可以将
dna1的前i-1个字符转变为dna2的前j-1个字符,然后替换一个字符,因此:
- 插入操作:我们可以将
-
综合考虑以上三种情况,对于字符不同的情况,我们可以得到状态转移方程:
-
最终结果
经过上述的状态定义和转移方程,我们可以通过动态规划的方式有效地计算出最少的编辑步骤。最终的答案就是 dp[n][m],其中 n 和 m 分别表示 dna1 和 dna2 的长度。
代码
int solution(std::string dna1, std::string dna2) {
// Please write your code here
int n = dna1.size();
int m = dna2.size();
std::vector<std::vector<int>> dp(n+1, std::vector<int>(m+1, 0));
for (int j = 1; j <= m; j++) {
dp[0][j] = j;
}
for (int i = 1; i <= n; i++) {
dp[i][0] = i;
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (dna1[i - 1] == dna2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1];
} else {
dp[i][j] = std::min(dp[i - 1][j - 1] + 1,
std::min(dp[i - 1][j] + 1, dp[i][j - 1] + 1));
}
}
}
return dp[n][m];
}