青训营X豆包MarsCode 技术训练营第一课 | 豆包MarsCode AI 刷题

29 阅读3分钟

小R正在研究的问题是一个经典的字符串编辑距离问题,也称为Levenshtein距离。这个问题要求计算将一个字符串(在这个场景中是受损的DNA序列 dna1)转换成另一个字符串(未受损的DNA序列 dna2)所需的最少编辑步骤数。允许的编辑操作包括:

  1. 增加一个碱基(插入) :在DNA序列的某个位置插入一个碱基。
  2. 删除一个碱基(删除) :从DNA序列的某个位置删除一个碱基。
  3. 替换一个碱基(替换) :将DNA序列中的某个碱基替换为另一个碱基。

为了解决这个问题,我们可以使用动态规划(Dynamic Programming, DP)的方法。动态规划通过构建一个二维数组 dp 来存储子问题的解,其中 dp[i][j] 表示将 dna1 的前 i 个字符转换成 dna2 的前 j 个字符所需的最少编辑步骤数。

动态规划分析

  1. 初始化

    • 当 i = 0 时,意味着 dna1 是空字符串。此时,将空字符串转换成 dna2 的前 j 个字符需要 j 次插入操作。因此,dp[0][j] = j
    • 当 j = 0 时,意味着 dna2 是空字符串。此时,将 dna1 的前 i 个字符转换成空字符串需要 i 次删除操作。因此,dp[i][0] = i
  2. 状态转移

    • 如果 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)
    
  3. 结果

    • 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))),但它已经足够高效地解决了大多数实际问题。