14 DNA序列还原
给定一段受损的 DNA 碱基序列 dna1,在每次只操作一个碱基的情况下,将其以最少的操作步骤将其还原到未受损的 DNA 碱基序列 dna2。
只可以对 DNA 碱基序列中的一个碱基进行三种操作:
- 增加一个碱基
- 去除一个碱基
- 替换一个碱基
输入描述
输入包含两行:
- 第一行为第一段受损的 DNA 碱基序列
dna1 - 第二行为第二段未受损的 DNA 碱基序列
dna2
限制条件:
0 <= dna1.length, dna2.length <= 500dna1和dna2由大写英文字母A、G、C、T组成
输出描述
输出一个整数,表示从 dna1 转换到 dna2 所需的最小操作步骤数。
示例
示例 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)
解题思路
要解决这个问题,我们需要计算将一个字符串转换为另一个字符串所需的最小操作次数,其中每次操作可以是插入、删除或替换一个字符。这实际上是经典的**编辑距离(Levenshtein距离)**问题。
1. 编辑距离概述
编辑距离是衡量两个字符串之间相似度的指标,表示将一个字符串转换成另一个字符串所需的最少编辑操作次数。允许的编辑操作包括:
- 插入一个字符
- 删除一个字符
- 替换一个字符
2. 动态规划(Dynamic Programming)方法
我们可以使用动态规划来高效地计算编辑距离。具体步骤如下:
2.1. 定义状态
设 dp[i][j] 表示将 dna1 的前 i 个字符转换为 dna2 的前 j 个字符所需的最小操作次数。
2.2. 状态转移方程
-
如果当前字符相同(即
dna1[i-1] == dna2[j-1]),则不需要额外的操作:dp[i][j] = dp[i-1][j-1] -
如果当前字符不同,我们考虑三种可能的操作,并选择其中最小的一个加一:
dp[i][j] = min( dp[i-1][j-1] + 1, // 替换 dp[i-1][j] + 1, // 删除 dp[i][j-1] + 1 // 插入 )
2.3. 初始条件
-
将空字符串转换为长度为
j的字符串需要j次插入操作:dp[0][j] = j -
将长度为
i的字符串转换为空字符串需要i次删除操作:dp[i][0] = i
2.4. 计算顺序
从 i = 1 到 m(m 为 dna1 的长度),以及从 j = 1 到 n(n 为 dna2 的长度),依次计算 dp[i][j]。
3. 时间与空间复杂度
- 时间复杂度:
O(m * n),其中m和n分别是dna1和dna2的长度。 - 空间复杂度:
O(m * n),用于存储dp矩阵。
算法实现
public class Main {
public static int solution(String dna1, String dna2) {
int m = dna1.length();
int n = dna2.length();
// 创建一个 (m+1) x (n+1) 的DP表
int[][] dp = new int[m + 1][n + 1];
// 初始化第一列和第一行
for (int i = 0; i <= m; i++) {
dp[i][0] = i; // 将dna1的前i个字符转换为空需要i次删除
}
for (int j = 0; j <= n; j++) {
dp[0][j] = j; // 将空转换为dna2的前j个字符需要j次插入
}
// 填充DP表
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (dna1.charAt(i - 1) == dna2.charAt(j - 1)) {
dp[i][j] = dp[i - 1][j - 1]; // 无需操作
} else {
dp[i][j] = Math.min(
dp[i - 1][j - 1], // 替换
Math.min(
dp[i - 1][j], // 删除
dp[i][j - 1] // 插入
)
) + 1;
}
}
}
return dp[m][n];
}
public static void main(String[] args) {
// 测试用例
System.out.println(solution("AGCTTAGC", "AGCTAGCT") == 2); // 输出: true
System.out.println(solution("AGCCGAGC", "GCTAGCT") == 4); // 输出: true
// 其他测试用例
System.out.println(solution("", "") == 0); // 输出: true
System.out.println(solution("A", "G") == 1); // 输出: true
System.out.println(solution("AGT", "AGT") == 0); // 输出: true
System.out.println(solution("AGT", "AGTC") == 1); // 输出: true
System.out.println(solution("AGT", "AG") == 1); // 输出: true
System.out.println(solution("AGT", "CGT") == 1); // 输出: true
}
}