问题描述
小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,其中dp[i][j]表示将dna1的前i个字符转换成dna2的前j个字符所需的最少编辑步骤。
1. DP数组的初始化
dp[i][0]:将dna1的前i个字符转换成空字符串,需要i次删除操作,因此dp[i][0] = i。dp[0][j]:将空字符串转换成dna2的前j个字符,需要j次插入操作,因此dp[0][j] = j。
2. 状态转移方程
-
如果
dna1[i-1] == dna2[j-1],表示当前字符相同,不需要额外操作,直接继承前一状态:dp[i][j] = dp[i-1][j-1]。 -
如果
dna1[i-1] != dna2[j-1],则可以进行以下三种操作:- 插入:将
dna1的前i个字符转换成dna2的前j-1个字符,再插入dna2[j-1],所需步骤为dp[i][j-1] + 1。 - 删除:将
dna1的前i-1个字符转换成dna2的前j个字符,再删除dna1[i-1],所需步骤为dp[i-1][j] + 1。 - 替换:将
dna1的前i-1个字符转换成dna2的前j-1个字符,再将dna1[i-1]替换为dna2[j-1],所需步骤为dp[i-1][j-1] + 1。
- 插入:将
取三者的最小值即为dp[i][j]的值:
-
可视化示例:
以下是对于dna1 = "ABC", dna2 = "AC"的dp数组填充过程的可视化示例:
dp[i][j] | j = 0 | j = 1(A) | j = 2(C) |
|---|---|---|---|
i = 0 | 0 | 1(插入A) | 2(插入A、C) |
i = 1(A) | 1(删除A) | 0(相同字符) | 1(替换B为C) |
i = 2(AB) | 2(删除A、B) | 1(删除B) | 1(相同字符) |
i = 3(ABC) | 3(删除A、B、C) | 2(删除C) | 2(删除B) |
-
首先初始化第一行和第一列。第一行表示将空字符串转换为
dna2的不同长度前缀所需的操作数,即依次插入字符,所以dp[0][1] = 1(插入A),dp[0][2] = 2(插入A、C)。第一列表示将dna1的不同长度前缀转换为空字符串所需的操作数,即依次删除字符,如dp[1][0] = 1(删除A),dp[2][0] = 2(删除A、B)等。 -
当
i = 1,j = 1时,dna1[0](A)和dna2[0](A)相同,所以dp[1][1] = 0,无需额外操作。 -
当
i = 1,j = 2时,dna1[0](A)和dna2[1](C)不同。此时有三种选择:- 删除:
dp[0][2] + 1 = 3(先将空字符串转换为AC,再删除A,但这不是最小值)。 - 插入:
dp[1][1] + 1 = 1(先将A转换为A,再插入C)。 - 替换:
dp[0][1] + 1 = 2(先将空字符串转换为A,再替换A为C)。取最小值,所以dp[1][2] = 1(这里实际是替换操作)。
- 删除:
-
以此类推,完成整个
dp数组的填充,最终答案是dp[3][2] = 2。
3. 最终结果
最终答案为dp[m][n],其中m和n分别是dna1和dna2的长度。
完整的Python实现如下:
def solution(dna1, dna2):
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
# 动态规划填表
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]
-
时间复杂度:
O(m * n),其中m和n分别是dna1和dna2的长度。我们需要填充一个m * n大小的DP表。 -
空间复杂度:
O(m * n),用于存储DP表。如果仅需要最终答案,可以优化空间复杂度至O(min(m, n))。