动态规划(Dynamic Programming, DP)是解决复杂问题的一个强大工具,它将问题分解成更小的子问题,并使用这些子问题的解决方案来构建整体问题的解决方案。在深入探讨最短编辑距离问题之前,让我们先理解什么是动态规划,以及如何通过动态规划的视角来看待这个问题。
原题链接:72. 编辑距离 - 力扣(LeetCode)
动态规划分析
动态规划的核心
动态规划通常用于求解最优化问题。其核心思想包括两个主要部分:
-
最优子结构:问题的最优解包含其子问题的最优解。这意味着我们可以通过合并子问题的最优解来构造整个问题的最优解。
-
重叠子问题:在解决问题的过程中,问题被分解成若干个子问题,其中很多子问题是重复的。
最短编辑距离的动态规划解法
在最短编辑距离问题中,我们要将一个字符串word1
转换成另一个字符串word2
,并且我们希望所需的操作次数尽可能少。这里的操作包括插入、删除和替换字符。
集合定义
在这个问题中,我们定义f[i][j]
为从word1
的前i
个字符转换到word2
的前j
个字符所需的最少操作次数。这个定义本身就隐含了一个最优子结构的性质,即要得到f[i][j]
的值,我们可以依赖于f[i-1][j]
、f[i][j-1]
和f[i-1][j-1]
的值,这些都是更小的子问题。
属性
在这个场景下,我们关注的属性是最小值(min),因为我们要找的是最少的操作次数。
转移方程
为了构建f[i][j]
,我们考虑以下三种可能的最后一步操作:
-
插入:我们可以先将
word1
的前i
个字符转换为word2
的前j-1
个字符,然后在末尾插入word2
的第j
个字符。这给我们f[i][j-1] + 1
。 -
删除:我们可以先将
word1
的前i-1
个字符转换为word2
的前j
个字符,然后删除word1
的第i
个字符。这给我们f[i-1][j] + 1
。 -
替换或保持:如果
word1[i]
与word2[j]
相同,我们不需要任何操作,只需要保持即可。如果它们不同,我们需要将word1[i]
替换为word2[j]
,这给我们f[i-1][j-1] + 1
(如果不同)或f[i-1][j-1]
(如果相同)。
综上所述,转移方程可以表示为:
f[i][j] = min( f[i][j - 1] + 1,
f[i - 1][j] + 1,
f[i - 1][j - 1] + (word1[i] != word2[j])
);
其中,(word1[i] != word2[j]) 是一个指示函数,当word1[i]
不等于word2[j]
时值为1,否则为0。
实现
基于上述分析,我们可以实现动态规划解法来解决最短编辑距离问题。
class Solution {
public int minDistance(String word1, String word2) {
int n = word1.length();
int m = word2.length();
int[][] f = new int[505][505];
// 有一个字符串为空串
if (n * m == 0) {
return n + m;
}
for (int i = 1; i <= n; i++ ) {
f[i][0] = i;
}
for (int i = 1; i <= m; i++ ) {
f[0][i] = i;
}
for (int i = 1; i <= n; i++ ) {
for (int j = 1; j <= m; j++ ) {
f[i][j] = Math.min(f[i - 1][j] + 1, f[i][j - 1] + 1);
f[i][j] = Math.min(f[i][j], f[i - 1][j - 1] + (word1.charAt(i - 1) == word2.charAt(j - 1) ? 0 : 1));
}
}
return f[n][m];
}
}
import java.util.*;
public class Main{
public static void main(String[] args) {
Scanner sca = new Scanner(System.in);
int N = 1010; // 假设字符串的最大长度
char[] a = new char[N];
char[] b = new char[N];
int[][] f = new int[N][N]; // 动态规划数组
int n = sca.nextInt(); // word1的长度
String A = sca.next(); // word1
int m = sca.nextInt(); // word2的长度
String B = sca.next(); // word2
// 初始化边界条件
for (int i = 1; i <= n; i++ ) {
a[i] = A.charAt(i - 1);
f[i][0] = i;
}
for (int i = 1; i <= m; i++ ) {
b[i] = B.charAt(i - 1);
f[0][i] = i;
}
// 动态规划填表
for (int i = 1; i <= n; i++ ) {
for (int j = 1; j <= m; j++ ) {
f[i][j] = Math.min(f[i][j - 1] + 1, f[i - 1][j] + 1);
if (a[i] == b[j]) f[i][j] = Math.min(f[i][j], f[i - 1][j - 1]);
else f[i][j] = Math.min(f[i][j], f[i - 1][j - 1] + 1);
}
}
System.out.println(f[n][m]); // 输出结果
}
}
时间复杂度
这个解法的时间复杂度为O(nm)
,其中n
和m
分别是字符串word1
和word2
的长度。这是因为我们需要填充一个n x m
的二维数组。