DNA序列还原
问题描述
给定一段受损的 DNA 碱基序列 dna1,在每次只操作一个碱基的情况下,将其以最少的操作步骤将其还原到未受损的 DNA 碱基序列 dna2。
只可以对 DNA 碱基序列中的一个碱基进行三种操作:
- 增加一个碱基
- 去除一个碱基
- 替换一个碱基
输入描述:
输入两段 DNA 碱基序列,每段分一行输入
第一行为第一段受损的 DNA 碱基序列 dna1
第二行为第二段未受损的 DNA 碱基序列 dna2
输出描述:
最小操作步骤数
备注:
0 <= dna1.length, dna2.length <= 500
dna1 和 dna2 由大写英文字母 A、G、C、T 组成
示例 1
输入
AGCTTAGC
AGCTAGCT
输出
2
说明
AGCTTAGC -> AGCTAGC(删除 T)
AGCTAGC -> AGCTAGCT(增加 T)
示例 2
输入
AGCCGAGC
GCTAGCT
输出
4
说明
AGCCGAGC -> GCCGAGC(删除 A)
GCCGAGC -> GCTGAGC(将 C 替换为 T)
GCTGAGC -> GCTAGC(删除 G)
GCTAGC -> GCTAGCT(增加 T)
要解决这个问题,我们可以使用动态规划(Dynamic Programming, DP)的方法。这个方法非常适合解决涉及字符串编辑距离的问题,比如 DNA 碱基序列的转换。我们将使用经典的编辑距离算法(也称为莱文斯坦距离算法)。
动态规划方法
我们定义一个二维数组 dp,其中 dp[i][j] 表示将 dna1 的前 i 个字符转换为 dna2 的前 j 个字符所需的最小操作数。
状态转移方程
-
初始状态:
dp[0][0] = 0,因为将空字符串转换为空字符串不需要任何操作。dp[i][0] = i,因为将前i个字符的dna1转换为空字符串需要i次删除操作。dp[0][j] = j,因为将空字符串转换为前j个字符的dna2需要j次插入操作。
-
状态转移方程:
-
如果
dna1[i-1] == dna2[j-1],则dp[i][j] = dp[i-1][j-1],因为最后一个字符相同,不需要额外操作。 -
如果
dna1[i-1] != dna2[j-1],则需要考虑三种操作:
- 插入操作:
dp[i][j] = dp[i][j-1] + 1 - 删除操作:
dp[i][j] = dp[i-1][j] + 1 - 替换操作:
dp[i][j] = dp[i-1][j-1] + 1要解决将受损的 DNA 序列恢复到原始序列的问题,我们可以使用动态规划的方法。这是一个典型的编辑距离问题,也称为莱文斯坦距离问题。
- 插入操作:
-
问题理解
给定两个 DNA 序列:
dna1:受损的序列。dna2:原始的序列。
我们的目标是通过最少的操作次数将 dna1 转换为 dna2。允许的操作有:
- 插入一个碱基。
- 删除一个碱基。
- 替换一个碱基。
动态规划方法
我们使用一个二维数组 dp,其中 dp[i][j] 表示将 dna1 的前 i 个字符转换为 dna2 的前 j 个字符所需的最小操作数。
初始化
-
基本情况:
dp[0][0] = 0:空字符串转为空字符串不需要操作。dp[i][0] = i:将dna1的前i个字符转为空字符串需要i次删除。dp[0][j] = j:空字符串转为dna2的前j个字符需要j次插入。
状态转移
对于每一对索引 ii 和 jj,我们计算 dp[i][j]:
-
如果当前字符相同(
dna1[i-1] == dna2[j-1]),则不需要额外操作:dp[i][j] = dp[i-1][j-1]。 -
如果字符不同(
dna1[i-1] != dna2[j-1]),需要考虑三种操作:- 插入:
dp[i][j] = dp[i][j-1] + 1。 - 删除:
dp[i][j] = dp[i-1][j] + 1。 - 替换:
dp[i][j] = dp[i-1][j-1] + 1。
- 插入:
公式为:
复杂度
该算法的时间复杂度是 ,其中 和 分别是 dna1 和 dna2 的长度。空间复杂度也是 ,因为需要存储 dp 表。
- 综合起来就是:
def solution(dna1, dna2):
m, n = len(dna1), len(dna2)
dp = [[0] * (n + 1) for _ in range(m + 1)]
for i in range(1, m+1):
dp[i][0] = i
for j in range(1, n+1):
dp[0][j] = j
dp[0][0] = 0
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)
# Please write your code here
return dp[m][n]
if __name__ == "__main__":
# You can add more test cases here
print(solution("AGCTTAGC", "AGCTAGCT") == 2 )
print(solution("AGCCGAGC", "GCTAGCT") == 4)