问题描述
小R正在研究DNA序列,他需要一个函数来计算将一个受损DNA序列(dna1)转换成一个未受损序列(dna2)所需的最少编辑步骤。编辑步骤包括:增加一个碱基、删除一个碱基或替换一个碱基。
输入:dna1和dna2,分别表示受损DNA序列和未受损DNA序列。
输出:转换所需的最小编辑距离,即转换所需的最少编辑步骤。
测试样例
样例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个字符所需的最小操作步骤数。
- 我们使用一个二维数组
-
初始化:
dp[0][j]表示将空字符串转换为dna2的前j个字符,需要j次插入操作。dp[i][0]表示将dna1的前i个字符转换为空字符串,需要i次删除操作。
-
状态转移:
-
如果
dna1[i-1] == dna2[j-1],则dp[i][j] = dp[i-1][j-1],即不需要任何操作。 -
否则,
dp[i][j]可以通过以下三种操作之一得到:- 插入:
dp[i][j-1] + 1 - 删除:
dp[i-1][j] + 1 - 替换:
dp[i-1][j-1] + 1
- 插入:
-
取这三种操作的最小值作为
dp[i][j]。
-
-
最终结果:
- 返回
dp[m][n],即dna1转换为dna2所需的最小操作步骤数。
- 返回
通过动态规划的思想,我们使用一个二维数组 dp 来记录将 dna1 转换为 dna2 所需的最小操作步骤数。初始化部分处理边界情况,状态转移部分根据字符是否相等来决定操作数,最终返回结果。
复杂度分析
- 时间复杂度:
O(m * n),其中m是dna1的长度,n是dna2的长度。 - 空间复杂度:
O(m * n),用于存储dp数组。
这个算法的时间复杂度和空间复杂度都是 O(m * n),其中 m 和 n 分别是 dna1 和 dna2 的长度。这种复杂度在处理中等长度的字符串时是可接受的,但对于非常长的字符串可能会导致性能问题。
代码实现
public class Main {
public static int solution(String dna1, String dna2) {
int m = dna1.length();
int n = dna2.length();
// 创建一个二维数组 dp
int[][] dp = new int[m + 1][n + 1];
// 初始化 dp 数组
for (int i = 0; i <= m; i++) {
dp[i][0] = i;
}
for (int j = 0; j <= n; j++) {
dp[0][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) {
// You can add more test cases here
System.out.println(solution("AGCTTAGC", "AGCTAGCT") == 2);
System.out.println(solution("AGCCGAGC", "GCTAGCT") == 4);
}
}