问题描述
小R正在研究DNA序列,他需要一个函数来计算将一个受损DNA序列(dna1)转换成一个未受损序列(dna2)所需的最少编辑步骤。编辑步骤包括:增加一个碱基、删除一个碱基或替换一个碱基。
测试样例
样例1:
输入:
dna1 = "AGT",dna2 = "AGCT"
输出:1
样例2:
输入:
dna1 = "AACCGGTT",dna2 = "AACCTTGG"
输出:4
样例3:
输入:
dna1 = "ACGT",dna2 = "TGC"
输出:3
样例4:
输入:
dna1 = "A",dna2 = "T"
输出:1
样例5:
输入:
dna1 = "GGGG",dna2 = "TTTT"
输出:4
解题思路
这个问题可以看作是求两个字符串之间的编辑距离问题,即将一个字符串通过插入、删除、替换字符等操作变成另一个字符串所需的最少操作次数。对于字符串中的每个字符,我们都需要基于前面的字符操作判断如何使当前的操作次数能够尽可能的少。显然可以采用动态规划的方式来解决,通过构建一个二维数组dp[i][j],用来记录将dna1的前i个字符转换为dna2的前j个字符所需的最少编辑步骤。
以下是基本步骤解释:
-
首先初始化
dp[i][j]的第一行dp[0][j],表示将空字符串变成dna2的前j个字符,唯一的办法就是依次插入字符,所以操作次数就是j。然后初始化第一列dp[i][0],将dna1的前i个字符变成空字符串,只能依次删除字符,所以操作次数就是i。 -
然后通过两层循环遍历dna1和dna2的每个字符位置,判断两个字符串中该索引的字符是否相等,如果相等,则不需要进行任何编辑操作,
dp[i][j]就等于把dna1的前i-1个字符转换为dna2的前j-1个字符的最少编辑步骤,即dp[i-1][j-1]。如果不相等,则进行以下操作: -
替换操作:把
dna1的前i - 1个字符转换为dna2的前j - 1个字符的最少编辑步骤再加1次替换操作,即dp[i - 1][j - 1] + 1。 -
删除操作:删除
dna1的第i个字符,可以看作把dna1的前i-1个字符转换dna2的前j个字符的最少编辑步骤,再加1次删除操作,即dp[i-1][j]+1。 -
增加操作:在
dna1的第i个字符位置插入dna2的第j个字符,可以看作把dna1的前i个字符转换为dna2的前j-1个字符的最少编辑步骤,再加1次插入操作,即dp[i][j-1]+1。选择三种操作中编辑步骤最小的那个作为当前dp[i][j]的值。 -
最后二维数组
dp[m][n]就存储了将整个dna1序列转换为dna2序列所需的最少编辑步骤,然后直接返回这个值即可。
代码详解
function solution(dna1, dna2) {
let m = dna1.length
let n = dna2.length
let dp = new Array(m + 1).fill(0).map(item => new Array(n + 1).fill(0))
dp[0][0] = 0
for (let i = 1; i <= m; i++) {
dp[i][0] = i
}
for (let j = 1; j <= n; j++) {
dp[0][j] = j
}
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
if (dna1[i - 1] === dna2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1]
} else {
dp[i][j] = Math.min(dp[i - 1][j - 1] + 1, dp[i - 1][j] + 1, dp[i][j - 1] + 1)
}
}
}
return dp[m][n];
}