述
小R正在研究DNA序列,他需要一个函数来计算将一个受损DNA序列(dna1)转换成一个未受损序列(dna2)所需的最少编辑步骤。编辑步骤包括:增加一个碱基、删除一个碱基或替换一个碱基。
测试样例
样例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"
输出:4public 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);
}
}
解题思路
-
定义状态:我们可以使用一个二维数组
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[i][0]和dp[0][j]的初始化表示将空字符串转换为非空字符串的操作数。 - 状态转移:根据当前字符是否相等,选择不同的操作(插入、删除、替换)并取最小值。
- 返回结果:
dp[m][n]即为将dna1转换为dna2的最小操作数。
动态规划算法如何应用到这个问题中? 动态规划(Dynamic Programming, DP)是一种通过将问题分解为子问题并存储子问题的解来解决复杂问题的方法。在这个问题中,我们可以将DNA序列的编辑距离问题分解为更小的子问题,并通过动态规划来解决。
解题思路
-
定义状态:
- 我们定义一个二维数组
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+1) x (n+1),其中m和n分别是dna1和dna2的长度。
- 创建一个二维数组
-
初始化边界条件:
- 对于
dp[i][0],表示将dna1的前i个字符转换为空字符串,需要i次删除操作。 - 对于
dp[0][j],表示将空字符串转换为dna2的前j个字符,需要j次插入操作。
- 对于
-
状态转移方程:
- 对于每个
i和j,如果dna1[i-1] == dna2[j-1],则dp[i][j] = dp[i-1][j-1]。 - 否则,
dp[i][j] = min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1]) + 1。
- 对于每个
-
最终结果:
dp[m][n]即为将dna1转换为dna2的最小操作数。
示例
假设 dna1 = "AGT",dna2 = "AGCT",我们可以通过以下步骤来填充 dp 数组:
| | A | G | C | T |
---|---|---|---|---|---|
| 0 | 1 | 2 | 3 | 4 |
A | 1 | 0 | 1 | 2 | 3 |
G | 2 | 1 | 0 | 1 | 2 |
T | 3 | 2 | 1 | 1 | 1 |
最终结果 dp[3][4] = 1,表示将 "AGT" 转换为 "AGCT" 需要的最少操作数为 1。
总结
通过动态规划,我们可以将复杂的问题分解为更小的子问题,并通过状态转移方程逐步求解。这种方法不仅适用于DNA序列的编辑距离问题,还可以应用于其他需要最小操作数的场景。