0%

动态规划——编辑距离

动态规划——编辑距离 问题汇总

判断子序列

给定字符串 s 和 t ,判断 s 是否为 t 的子序列

https://leetcode.cn/problems/is-subsequence/

dp[i][j]表示 s[0...i-1]是否为t[0...j-1]的子序列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Solution {
public boolean isSubsequence(String s, String t) {
if (s.length() > t.length()) {
return false;
}
int N = t.length();
int M = s.length();
char[] tc = t.toCharArray();
char[] sc = s.toCharArray();
boolean[][] dp = new boolean[N+1][M+1];
for (int i = 0; i <= N; i++) {
dp[i][0] = true;
}
for (int i = 1; i <= M; i++) {
dp[0][i] = false;
}
for (int i = 1; i <= N; i++) {
for (int j = 1; j <= M ; j++) {
if (tc[i-1] == sc[j-1]) {
dp[i][j] = dp[i-1][j-1];
} else {
dp[i][j] = dp[i-1][j];
}
}
}
return dp[N][M];
}
}

dp数组变为int[][]类型,即为求t到s的删除距离

判断子序列问题优化:空间压缩

当dp公式只与上一层有关时,可以将二维dp数组优化为一维dp数组,降低空间复杂度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Solution {
public boolean isSubsequence(String s, String t) {
if (s.length() > t.length()) {
return false;
}
int N = t.length();
int M = s.length();
char[] sc = s.toCharArray();
char[] tc = t.toCharArray();
boolean[] dp = new boolean[M+1];
dp[0] = true;
for (int i = 1; i <= M; i++) {
dp[i] = false;
}
for (int i = 1; i <= N; i++) {
for (int j = M; j >= 1; j--) {
if (sc[j-1] == tc[i-1]) {
dp[j] = dp[j-1];
}
}
}
return dp[M];
}
}

删除成为子串

给定两个字符串s1和s2,问s2最少删除多少字符可以成为s1的子串?比如 s1 = "abcde",s2 = "axbc",s2删掉'x'即可,返回1

思路:求s1的所有子串,求s2到每个子串的删除距离(同上题)

思考点:如何复用dp数组

Tips:具有相同前缀的s1的子串,可以共用一个dp数组,即在求一次dp方程时,一次性求出很多s1子串

1
2
3
4
5
public Solution {
public int minCost(String s1, String s2) {
// 待补充
}
}

不同的子序列

给定一个字符串 s 和一个字符串 t ,计算在 s 的子序列中 t 出现的个数

https://leetcode.cn/problems/distinct-subsequences/

dp[i][j]:以i-1为结尾的s子序列中出现以j-1为结尾的t的个数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Solution {
public int numDistinct(String s, String t) {
if (s.length() < t.length()) {
return 0;
}
int N = s.length();
int M = t.length();
int[][] dp = new int[N+1][M+1];
// 初始化dp边界
for (int i = 0; i <= N; i++) {
dp[i][0] = 1;
}
for (int i = 1; i <= M; i++) {
dp[0][i] = 0;
}
// 根据dp方程填表
for (int i = 1; i <= N; i++) {
for (int j = 1; j <= M; j++) {
if (s.charAt(i-1) == t.charAt(j-1)) {
dp[i][j] = dp[i-1][j-1] + dp[i-1][j];
} else {
dp[i][j] = dp[i-1][j];
}
}
}
return dp[N][M];
}
}

同样可以用空间压缩优化,与第一题类似

两个字符串的删除操作

给定两个单词 word1 和 word2 ,返回使得 word1 和 word2 相同所需的最小步数每步 可以删除任意一个字符串中的一个字符。

https://leetcode.cn/problems/delete-operation-for-two-strings/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
public int minDistance(String word1, String word2) {
int N = word1.length();
int M = word2.length();
int[][] dp = new int[N+1][M+1];
for (int i = 0; i <= N; i++) {
dp[i][0] = i;
}
for (int i = 0; i <= M; i++) {
dp[0][i] = i;
}
for (int i = 1; i <= N; i++) {
for (int j = 1; j <= M; j++) {
if (word1.charAt(i-1) == word2.charAt(j-1)) {
dp[i][j] = dp[i-1][j-1];
} else {
dp[i][j] = Math.min(dp[i-1][j-1] + 2, Math.min(dp[i-1][j] + 1, dp[i][j-1] + 1));
}
}
}
return dp[N][M];
}
}

编辑距离

给你两个单词 word1 和 word2, 请返回将 word1 转换成 word2 所使用的最少操作数 。你可以对一个单词进行如下三种操作:插入一个字符,删除一个字符,替换一个字符

https://leetcode.cn/problems/edit-distance/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Solution {
public int minDistance(String word1, String word2) {
int N = word1.length();
int M = word2.length();
char[] s = word1.toCharArray();
char[] t = word2.toCharArray();
int[][] dp = new int[N+1][M+1];
// 初始化dp边界
for (int i = 0; i <= N; i++) {
dp[i][0] = i;
}
for (int i = 0; i <= M; i++) {
dp[0][i] = i;
}
// 根据dp方程填表
for (int i = 1; i <= N; i++) {
for (int j = 1; j <= M; j++) {
if (s[i-1] == t[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[N][M];
}
}

总结

本文汇总了动态规划中常见的编辑距离问题,通过对每个问题的分析,可以发现:

  1. dp[i][j] 的含义一般都表示在s[0...i-1]与t[0...j-1]情况下的问题所求
  2. 边界一般都是考虑在dp[i][0]与dp[0][j]下的值
  3. dp方程具有很强的规律性,即在 s[i-1] 是否等于 t[j-1] 的两种情况下,考虑dp方程是什么
  4. 都可以使用空间压缩方法进行空间复杂度的优化