题目描述
计算将一个受损DNA序列转换成未受损序列所需的最少编辑步骤
给定两个字符串 dna1 和 dna2,分别表示受损的DNA序列和未受损的DNA序列。任务是编写一个函数,计算将 dna1 转换成 dna2 所需的最少编辑步骤。
解题思路
这个问题可以通过动态规划来解决。动态规划的思想是将问题分解成子问题,并利用子问题的解来构建原问题的解。具体来说,我们可以定义一个二维数组 dp,其中 dp[i][j] 表示将 dna1 的前 i 个字符转换成 dna2 的前 j 个字符所需的最少编辑步骤。
动态规划的核心思想
-
初始化:
dp[i][0]表示将dna1的前i个字符转换成空字符串所需的步骤,即删除所有字符,因此dp[i][0] = i。dp[0][j]表示将空字符串转换成dna2的前j个字符所需的步骤,即插入所有字符,因此dp[0][j] = j。
-
状态转移:
-
如果
dna1[i-1] == dna2[j-1],则dp[i][j] = dp[i-1][j-1],即无需编辑。 -
否则,
dp[i][j]可以通过以下三种方式之一获得最小值:- 删除
dna1的第i个字符:dp[i-1][j] + 1 - 插入
dna2的第j个字符:dp[i][j-1] + 1 - 替换
dna1的第i个字符为dna2的第j个字符:dp[i-1][j-1] + 1
- 删除
-
-
最终结果:
dp[m][n]即为将dna1转换成dna2所需的最少编辑步骤,其中m和n分别是dna1和dna2的长度。
详细步骤
-
初始化:
-
创建一个
(m+1) x (n+1)的二维数组dp,其中m是dna1的长度,n是dna2的长度。 -
初始化第一行和第一列:
dp[i][0] = i,表示将dna1的前i个字符转换成空字符串所需的步骤。dp[0][j] = j,表示将空字符串转换成dna2的前j个字符所需的步骤。
-
-
填充DP表:
- 遍历
dna1和dna2的每个字符,根据状态转移方程更新dp表。
- 遍历
-
返回结果:
- 返回
dp[m][n],即为最终结果。
- 返回
代码解析
def solution(dna1: str, dna2: str) -> int:
m, n = len(dna1), len(dna2)
dp = [[0] * (n + 1) for _ in range(m + 1)]
# 初始化第一行和第一列
for i in range(m + 1):
dp[i][0] = i # 删除所有字符
for j in range(n + 1):
dp[0][j] = j # 插入所有字符
# 填充DP表
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]
输入输出全过程分析实例
示例 1
输入:dna1 = "AGCTTAGC", dna2 = "AGCTAGCT"
-
初始化:
m = 8,n = 8dp表初始化为(9 x 9)的零矩阵。
-
初始化第一行和第一列:
dp[i][0] = i,表示将dna1的前i个字符转换成空字符串所需的步骤。dp[0][j] = j,表示将空字符串转换成dna2的前j个字符所需的步骤。
-
填充DP表:
i = 1,j = 1,dna1[0] == dna2[0],dp[1][1] = dp[0][0] = 0i = 1,j = 2,dna1[0] != dna2[1],dp[1][2] = min(dp[0][2] + 1, dp[1][1] + 1, dp[0][1] + 1) = 1i = 1,j = 3,dna1[0] != dna2[2],dp[1][3] = min(dp[0][3] + 1, dp[1][2] + 1, dp[0][2] + 1) = 2- ...
i = 8,j = 8,dna1[7] == dna2[7],dp[8][8] = dp[7][7] = 2
-
返回结果:
return 2
输出:2
示例 2
输入:dna1 = "AGCCGAGC", dna2 = "GCTAGCT"
-
初始化:
m = 8,n = 7dp表初始化为(9 x 8)的零矩阵。
-
初始化第一行和第一列:
dp[i][0] = i,表示将dna1的前i个字符转换成空字符串所需的步骤。dp[0][j] = j,表示将空字符串转换成dna2的前j个字符所需的步骤。
-
填充DP表:
i = 1,j = 1,dna1[0] != dna2[0],dp[1][1] = min(dp[0][1] + 1, dp[1][0] + 1, dp[0][0] + 1) = 1i = 1,j = 2,dna1[0] != dna2[1],dp[1][2] = min(dp[0][2] + 1, dp[1][1] + 1, dp[0][1] + 1) = 2i = 1,j = 3,dna1[0] == dna2[2],dp[1][3] = dp[0][2] = 2- ...
i = 8,j = 7,dna1[7] != dna2[6],dp[8][7] = min(dp[7][7] + 1, dp[8][6] + 1, dp[7][6] + 1) = 4
-
返回结果:
return 4
输出:4
学习心得
- 动态规划的思想:动态规划是一种通过将问题分解成子问题来解决问题的方法。在这个问题中,我们将整个字符串的编辑距离问题分解成一个个字符的编辑距离问题,从而高效地解决了问题。
- 状态转移方程:状态转移方程是动态规划的核心,它描述了如何从已知的状态推导出新的状态。在这个问题中,我们通过比较当前字符是否相等,来决定编辑步骤的最小值。
- 初始化的重要性:初始化是动态规划的第一步,正确的初始化可以确保后续的计算是正确的。在这个问题中,我们初始化了第一行和第一列,表示将一个字符串转换成空字符串或从空字符串转换成另一个字符串所需的步骤。