问题描述
小R正在研究DNA序列,他需要一个函数来计算将一个受损DNA序列(dna1)转换成一个未受损序列(dna2)所需的最少编辑步骤。编辑步骤包括:增加一个碱基、删除一个碱基或替换一个碱基。
问题解析
和力扣的编辑距离是一样的,采用多维动态规划来解决。
动态规划:
动态规划的核心思想是将问题分解为更小的子问题,并利用已经解决的子问题的解来构建更大问题的解。
那么我们可以定义dp[i][j]来表示将dna1的前i个字符转为dna2的前j的字符所需的最小编辑步骤。
i->0:需要i次删除
dp[i][0] = i
0->j:需要j次添加
dp[0][j] = j
- 都不为
0的情况下,则根据dna1[i - 1] 和 dna2[j - 1]的值判断dp[i][j]是否相对于dp[i-1][j-1]有变化。
for i:=1;i<=m;i++{
for j:=1;j<=n;j++{
if word1[i-1]==word2[j-1]{
dp[i][j]=dp[i-1][j-1]
}else{
dp[i][j]=min(dp[i-1][j],min(dp[i][j-1],dp[i-1][j-1]))+1
}
}
}
我们来画一个图理解一下:
这是做完第一步和第二步的样子
我们一步一步来把dp填满
dp[1][1]就是dna1的前一个字符串到dna2的前一个字符串的所需转换步骤,我们比较一下dna1[0]和dna2[0],因为A==A所以不需要转换,dp[1][1]=0(需要注意数组的下标和切片下标的区别,这就是为什么要比较dna1[i - 1] 和 dna2[j - 1]而不是dna1[i] 和 dna2[j]的原因)dp[1][2]就是dna1的前一个字符串到dna2的前两个字符串的所需转换步骤,我们比较一下dna1[0]和dna2[1],因为A!=G所以需要转换,因为要找到最小的转换次数就需要保证每一步都是最小的所以取min+1。而dp[i-1][j] + 1表示从dna1的前i-1个字符转换为dna2的前j个字符的最小步骤数,意味着删除dna1的第i个字符,从而使得前i-1个字符与dna2的前j个字符匹配;dp[i][j-1] + 1:表示从dna1的前i个字符转换为dna2的前j-1个字符的最小步骤数,加一意味着在dna1的末尾插入一个与dna2的第j个字符相同的字符以便匹配;dp[i-1][j-1] + 1:表示从dna1的前i-1个字符转换为dna2的前j-1个字符的最小步骤数,则加一是指需要将dna1的第i个字符替换为dna2的第j个字符- ......
下图是最后的填充结果:
完整的go代码:
m,n:=len(dna1),len(dna2)
dp:=make([][]int,m+1
for i:=range dp{
dp[i]=make([]int,n+1)
}
for i:=0;i<=m;i++{
dp[i][0]=i
}
for j:=0;j<=n;j++{
dp[0][j]=j
}
for i:=1;i<=m;i++{
for 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],min(dp[i][j-1],dp[i-1][j-1]))+1
}
}
}
return dp[m][n]
完整python代码:
def solution(dna1, dna2):
m = len(dna1)
n = len(dna2)
dp = [[0] * (n + 1) for _ in range(m + 1)]
for i in range(m + 1):
dp[i][0] = i # 从 dna1 的前 i 个字符到空串需要 i 步删除
for j in range(n + 1):
dp[0][j] = j # 从空串到 dna2 的前 j 个字符需要 j 步插入
# 填充矩阵
for i in range(1, m + 1):
for j in range(1, n + 1):
if dna1[i - 1] == dna2[j - 1]:
dp[i][j] = dp[i - 1][j - 1]
else:
dp[i][j] = min(
dp[i - 1][j] + 1,
dp[i][j - 1] + 1,
dp[i - 1][j - 1] + 1
)
return dp[m][n]