小R正在研究的问题是一个经典的字符串编辑距离问题,也称为Levenshtein距离。这个问题要求计算将一个字符串(在这个场景中是受损的DNA序列 dna1)转换成另一个字符串(未受损的DNA序列 dna2)所需的最少编辑步骤数。允许的编辑操作包括:
- 增加一个碱基(插入) :在DNA序列的某个位置插入一个碱基。
- 删除一个碱基(删除) :从DNA序列的某个位置删除一个碱基。
- 替换一个碱基(替换) :将DNA序列中的某个碱基替换为另一个碱基。
为了解决这个问题,我们可以使用动态规划(Dynamic Programming, DP)的方法。动态规划通过构建一个二维数组 dp 来存储子问题的解,其中 dp[i][j] 表示将 dna1 的前 i 个字符转换成 dna2 的前 j 个字符所需的最少编辑步骤数。
动态规划分析
-
初始化:
- 当
i = 0时,意味着dna1是空字符串。此时,将空字符串转换成dna2的前j个字符需要j次插入操作。因此,dp[0][j] = j。 - 当
j = 0时,意味着dna2是空字符串。此时,将dna1的前i个字符转换成空字符串需要i次删除操作。因此,dp[i][0] = i。
- 当
-
状态转移:
-
如果
dna1[i-1] == dna2[j-1],则dp[i][j] = dp[i-1][j-1],因为最后一个字符已经匹配,不需要额外的编辑步骤。 -
否则,
dp[i][j]可以通过以下三种操作中的最小值加1来计算:- 插入:
dp[i][j-1] + 1(在dna1的末尾插入一个字符以匹配dna2[j-1]) - 删除:
dp[i-1][j] + 1(从dna1中删除一个字符以匹配dna2的前缀) - 替换:
dp[i-1][j-1] + 1(将dna1[i-1]替换为dna2[j-1])
- 插入:
因此,状态转移方程为:
复制代码 dp[i][j] = min(dp[i-1][j-1] + (dna1[i-1] != dna2[j-1]), dp[i][j-1] + 1, dp[i-1][j] + 1) -
-
结果:
dp[m][n]就是将dna1的所有字符转换成dna2的所有字符所需的最少编辑步骤数,其中m和n分别是dna1和dna2的长度。
示例代码(Python)
python复制代码
def min_edit_distance(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
# 填充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], dp[i-1][j]) + 1
return dp[m][n]
# 示例
dna1 = "AGCTG"
dna2 = "AGTCG"
print("最少编辑步骤数:", min_edit_distance(dna1, dna2)) # 输出应该是1
这个算法的时间复杂度是 O(m×n),空间复杂度也是 O(m×n),其中 m 和 n 分别是两个DNA序列的长度。虽然这不是最优的空间复杂度(可以通过一些技巧将其降低到 O(n) 或 O(min(m,n))),但它已经足够高效地解决了大多数实际问题。