问题描述:
小R正在研究DNA序列,他需要一个函数来计算将一个受损DNA序列(dna1)转换成一个未受损序列(dna2)所需的最少编辑步骤。编辑步骤包括:增加一个碱基、删除一个碱基或替换一个碱基。
测试样例:
-
输入:
dna1 = "AGT", dna2 = "AGCT"- 输出:
1
- 输出:
-
输入:
dna1 = "AACCGGTT", dna2 = "AACCTTGG"- 输出:
4
- 输出:
-
输入:
dna1 = "ACGT", dna2 = "TGC"- 输出:
3
- 输出:
-
输入:
dna1 = "A", dna2 = "T"- 输出:
1
- 输出:
-
输入:
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所需的最少编辑步骤,其中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) {
// 你可以添加更多测试用例
System.out.println(solution("AGCTTAGC", "AGCTAGCT") == 2);
System.out.println(solution("AGCCGAGC", "GCTAGCT") == 4);
}
}
知识总结
新知识点:
- 动态规划:通过定义状态、初始化、状态转移和最终结果,解决编辑距离问题。
- 二维数组:使用二维数组来存储中间状态,便于计算和更新。
- 字符串操作:理解字符串的插入、删除和替换操作,并将其转化为动态规划的状态转移。
学习建议:
- 理解动态规划:动态规划是解决复杂问题的有效方法,建议多练习类似的题目,加深对状态定义和状态转移的理解。
- 多做练习:通过刷题巩固知识点,特别是动态规划相关的题目,可以提高解题能力。
- 总结经验:每次刷题后,总结解题思路和方法,形成自己的解题模板。