算法训练(leetcode)二刷第三十九天 | 115. 不同的子序列、583. 两个字符串的删除操作、72. 编辑距离
刷题记录
- *115. 不同的子序列
- 583. 两个字符串的删除操作
- 思路一:转求公共子序列
- 思路二:编辑距离(统计删除次数)
- 72. 编辑距离
*115. 不同的子序列
leetcode题目地址
编辑距离问题。
题目要求在s
串中查找t
串出现的次数。
dp数组含义:dp[i][j] 表示以s[i-1]
结尾的子串A中出现以t[j-1]
为结尾的子串B的个数
状态转移方程:
题目要求在s
串中查找t
串出现的次数,因此只考虑对s
串进行编辑。
- 当
s[i-1] == t[j-1]
时,dp[i][j] = dp[i-1][j-1] + dp[i-1][j],分别考察使用s[i-1]
和不使用s[i-1]
- dp[i-1][j-1]:(使用s[i-1])以
s[i-2]
结尾的子串A中出现以t[j-2]
为结尾的子串B的个数,也就是不考虑当前s串和t串
正在查看的当前字符,查看两个子串前一位的匹配情况(就是考虑将当前字符加入子串) - dp[i-1][j-1]:(不使用s[i-1])以
s[i-2]
结尾的子串A中出现以t[j-1]
为结尾的子串B的个数,也就是不考虑当前s串
正在查看的当前字符,即删除s[i-1]
- dp[i-1][j-1]:(使用s[i-1])以
时间复杂度:
O
(
n
∗
m
)
O(n*m)
O(n∗m)
空间复杂度:
O
(
n
∗
m
)
O(n*m)
O(n∗m)
// java
class Solution {
public int numDistinct(String s, String t) {
int len1 = s.length(), len2 = t.length();
char[] sArray = s.toCharArray(), tArray = t.toCharArray();
int[][] dp = new int[len1+1][len2+1];
for(int i=0; i<=len1; i++) dp[i][0] = 1;
int result = 0;
for(int i=1; i<=len1; i++){
for(int j=1; j<=len2; j++){
if(sArray[i-1] == tArray[j-1]){
dp[i][j] = dp[i-1][j-1] + dp[i-1][j];
} else {
dp[i][j] = dp[i-1][j];
}
}
}
return dp[len1][len2] % (int)(Math.pow(10, 9)+7);
}
}
583. 两个字符串的删除操作
leetcode题目地址
思路一:转求公共子序列
同样是编辑距离问题。
本题仅考虑删除问题,因此较简单。
先将问题转化:
- 原问题:每步可以删除任意一个字符串中的一个字符。
- 转化:先求两个字符串的公共子序列长度,然后两个子序列中各自多出来的其余字符就是要删除的。
统计公共子序列长度公共子序列长度是前面做过的(题解),因此直接把代码搬过来既可。
题目要求返回两个字符串总共删除的字符个数,设公共子序列长度为x,则A、B串中各自有长为x的子串,因此要删除的字符总数 = A串长度 + B串长度 - x * 2
时间复杂度:
O
(
n
∗
m
)
O(n * m)
O(n∗m)
空间复杂度:
O
(
n
∗
m
)
O(n * m)
O(n∗m)
// java
class Solution {
public int minDistance(String word1, String word2) {
char[] arr1 = word1.toCharArray(), arr2 = word2.toCharArray();
int len1 = arr1.length, len2 = arr2.length;
// dp[i][j]:以s[i-1]和t[j-1]的两个子串公共子序列长度
int[][] dp = new int[len1+1][len2+1];
// 状态转移方程:
// s[i-1]==t[j-1]: dp[i][j] = dp[i-1][j-1] + 1;
// s[i-1]!=t[j-1]: dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1]);
for(int i=1; i<=len1; i++){
for(int j=1; j<=len2; j++){
if(arr1[i-1] == arr2[j-1]) {
dp[i][j] = dp[i-1][j-1] + 1;
} else{
dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1]);
}
}
}
return len1 + len2 - dp[len1][len2]*2;
}
}
思路二:编辑距离(统计删除次数)
dp数组含义:dp[i][j]表示以word1[i-1]和word2[j-1]结尾的相同子串需要删除的最小步数
状态转移方程:
- word1[i-1] == word2[j-1]:
dp[i][j] = dp[i-1][j-1]
- word1[i-1] != word2[j-1]:
- 删除word1[i-1]:dp[i-1][j] + 1
- 删除word2[j-1]:dp[i][j-1] + 1
- 删除word1[i-1]和word2[j-1]:dp[i-1][j-1] + 2
综上,dp[i][j]更新为三者取最小,即:
dp[i][j] = min(dp[i-1][j] + 1, dp[i][j-1] + 1, dp[i-1][j-1] + 2)
PS: 这里dp[i-1][j-1] + 2其实相当于dp[i][j-1] + 1或dp[i-1][j],用dp[i][j-1] + 1举例:
dp[i][j-1] + 1是考虑word1[i-1]和不考虑word2[j-1],在这个基础上再不考虑word1[i-1]就是dp[i-1][j-1] + 1 + 1也就是dp[i-1][j-1] + 2,因此在第二部两字符不相等时,更新dp[i][j]仅需要比较两个串各自不考虑当前字符即可,即:dp[i][j] = min(dp[i-1][j] + 1, dp[i][j-1] + 1)
// java
class Solution {
public int minDistance(String word1, String word2) {
char[] arr1 = word1.toCharArray(), arr2 = word2.toCharArray();
int len1 = arr1.length, len2 = arr2.length;
int[][] dp = new int[len1+1][len2+1];
// dp数组含义:dp[i][j]表示以s[i-1]和t[j-1]结尾的相同子串需要删除的最小步数
// 状态转移方程
// s[i-1] == t[j-1]: dp[i][j] = dp[i-1][j-1]
// s[i-1] != t[j-1]:
// dp[i-1][j] + 1
// dp[i][j-1] + 1
// dp[i-1][j-1] + 2
// dp[i][j] = Math.min(Math.min(dp[i-1][j] + 1, dp[i][j-1] + 1), dp[i-1][j-1] + 2)
// 初始化
// dp[0][j] = j dp[i][0] = i
for(int i=0; i<=len1; i++) dp[i][0] = i;
for(int j=0; j<=len2; j++) dp[0][j] = j;
for(int i=1; i<=len1; i++){
for(int j=1; j<=len2; j++){
if(arr1[i-1] == arr2[j-1]){
dp[i][j] = dp[i-1][j-1];
}else{
dp[i][j] = Math.min(Math.min(dp[i-1][j]+1, dp[i][j-1]+1), dp[i-1][j-1]+2);
}
}
}
return dp[len1][len2];
}
}
72. 编辑距离
leetcode题目地址
dp数组含义:dp[i][j]表示以word1[i-1]和word2[j-1]结尾的相同串所需最少操作数
状态转移方程:
- word1[i-1]==word2[j-1]:
dp[i][j] = dp[i-1][j-1]
- word1[i-1]!=word2[j-1]:
- 删除word1[i-1](也就是word1[i-2]和word2[j-1]匹配,word1[i-1]多余): dp[i][j] = dp[i-1][j] + 1
- 向word1插入word2[j-1](也就是word1[i-1]和word2[j-2]匹配,但当前word2[j-1]缺少匹配): dp[i][j] = dp[i][j-1] + 1
- 替换word1[i-1]为word2[j-1](也就是word1[i-2]和word2[j-2]匹配,但word1[i-1]和word2[j-1]不匹配):dp[i][j] = dp[i-1][j-1] + 1
综上,三者取最小,即:
dp[i][j] = min(dp[i-1][j] + 1, dp[i][j-1] + 1, dp[i-1][j-1] + 1)
初始化:dp[i][0] = i, dp[0][j] = j
时间复杂度:
O
(
n
∗
m
)
O(n*m)
O(n∗m)
空间复杂度:
O
(
n
∗
m
)
O(n*m)
O(n∗m)
// java
class Solution {
public int minDistance(String word1, String word2) {
char[] arr1 = word1.toCharArray(), arr2 = word2.toCharArray();
int len1 = arr1.length, len2 = arr2.length;
int[][] dp = new int[len1+1][len2+1];
// dp数组含义:dp[i][j]表示以word1[i-1]和word2[j-1]结尾的相同串所需最少操作数
// 状态转移方程:
// word1[i-1]==word2[j-1]: dp[i][j] = dp[i-1][j-1]
// word1[i-1]!=word2[j-1]:
// 删除word1[i-1]: dp[i][j] = dp[i-1][j] + 1
// 向word1插入word2[j-1]: dp[i][j] = dp[i][j-1] + 1
// 替换word1[i-1]为word2[j-1]:dp[i][j] = dp[i-1][j-1] + 1
// 三者取最小
// 初始化:dp[i][0] = i dp[0][j] = j
for(int i=0; i<=len1; i++) dp[i][0] = i;
for(int j=0; j<=len2; j++) dp[0][j] = j;
for(int i=1; i<=len1; i++){
for(int j=1; j<=len2; j++){
if(arr1[i-1] == arr2[j-1]){
dp[i][j] = dp[i-1][j-1];
} else{
dp[i][j] = Math.min(Math.min(dp[i-1][j] + 1, dp[i][j-1] + 1), dp[i-1][j-1] + 1);
}
}
}
return dp[len1][len2];
}
}