问题描述
小R正在研究DNA序列,他需要一个函数来计算将一个受损DNA序列(dna1)转换成一个未受损序列(dna2)所需的最少编辑步骤。编辑步骤包括:增加一个碱基、删除一个碱基或替换一个碱基。
思路解析
-
问题本质
- 这是一个典型的序列变换问题,涉及两个序列的最优子结构问题。
- 动态规划的核心是找到两个子问题的递归关系,并通过填表的方式解决问题。
-
状态定义
- 定义一个二维DP数组
dp[i][j],表示将dna1的前i个字符转换为dna2的前j个字符所需的最少编辑操作次数。
- 定义一个二维DP数组
-
边界条件
-
当一个序列为空时,转换为另一个序列的操作数就是另一个序列的长度:
dp[i][0] = i表示dna1的前i个字符变为空串需要i次删除操作。dp[0][j] = j表示空串变为dna2的前j个字符需要j次插入操作。
-
-
递推公式
-
如果当前字符相等(
dna1[i-1] == dna2[j-1]),那么不需要额外的操作:css 复制代码 dp[i][j] = dp[i-1][j-1] -
如果当前字符不相等,则需要考虑三种操作:
-
删除操作:
dp[i-1][j] + 1 -
插入操作:
dp[i][j-1] + 1 -
替换操作:
dp[i-1][j-1] + 1 -
取三者的最小值:
css 复制代码 dp[i][j] = min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]) + 1
-
-
-
最终结果
- 最小的编辑步骤即为
dp[len(dna1)][len(dna2)]。
- 最小的编辑步骤即为
代码解析
1. 初始化
len1, len2 = len(dna1), len(dna2)
dp = [[0] * (len2 + 1) for _ in range(len1 + 1)]
- 创建一个大小为
(len1 + 1) x (len2 + 1)的DP表,表中的每一格初始值为0。 - 使用
len1+1和len2+1是为了包括空串的情况。
2. 填充边界条件
for i in range(1, len1 + 1):
dp[i][0] = i # dna1 的前 i 个字符变为空串需要 i 次删除
for j in range(1, len2 + 1):
dp[0][j] = j # 空串变为 dna2 的前 j 个字符需要 j 次插入
- 第一行和第一列分别表示从空串到目标序列或从源序列到空串的转换成本。
3. 填表过程
for i in range(1, len1 + 1):
for j in range(1, len2 + 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], dp[i][j - 1], dp[i - 1][j - 1]) + 1
- 双层循环遍历
dna1和dna2的每个字符。 - 当字符相等时,无需额外操作,继承左上角的值。
- 当字符不相等时,取三种操作的最小值,并加1表示当前的操作。
4. 返回结果
return dp[len1][len2]
- 返回DP表的右下角值,即将
dna1转换为dna2所需的最小编辑步骤。
5. 主函数
- 保留本身提供的测试用例,验证函数的正确性。