一个人的朝圣 — LeetCode打卡第54-55天
知识总结
今天学习动态规划里面的编辑距离问题, 有几道类似的题目, 可以使用相同的模板来做
Leetcode 392. 判断子序列
题目链接
题目说明
给定字符串 s 和 t ,判断 s 是否为 t 的子序列。
字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"是"abcde"的一个子序列,而"aec"不是)。
进阶:
如果有大量输入的 S,称作 S1, S2, … , Sk 其中 k >= 10亿,你需要依次检查它们是否为 T 的子序列。在这种情况下,你会怎样改变代码?
代码说明
方法1 , 使用双指针可以比较快的做出了, 相等的话两个指针都往前进, 不相等就只走t的指针
class Solution {
public boolean isSubsequence(String s, String t) {
int index = 0;
int len1 = s.length(), len2 = t.length();
if(len1 == 0) return true;
if(len2 == 0) return false;
for(int i = 0; i < t.length(); i++){
if(s.charAt(index) == t.charAt(i)){
index++;
if(index == s.length()){
return true;
}
}
}
return false;
}
}
方法2, 当然我们还是要学会用动态规划的思想来解决该系列问题
这个问题可以转换成找s, t的最大公共序列的长度, 如果该长度=s.长度, 则t包含了s.
class Solution {
public boolean isSubsequence(String s, String t) {
int len1 = s.length(), len2 = t.length();
// dp[i][j] 代表着以i-1结尾的s和j-1结尾的t的最大公共子序列长度
int[][] dp = new int[len1+1][len2+1];
for(int i = 1; i <= len1; i++){
for(int j = 1; j <=len2; j++){
if(s.charAt(i-1) == t.charAt(j-1)){
dp[i][j] = dp[i-1][j-1] + 1;
}else{
dp[i][j] = dp[i][j-1];
}
}
// System.out.println(Arrays.toString(dp[i]));
}
return dp[len1][len2] == len1;
}
}
Leetcode 115. 不同的子序列
题目链接
题目说明
给你两个字符串 s 和 t ,统计并返回在 s 的 子序列 中 t 出现的个数。
题目数据保证答案符合 32 位带符号整数范围。
代码说明
这个题目还挺难的,第一次做的话可能没有思路。
难点在于递推公式如何求出来:
- dp数组 // dp[i][j] 表示以i-1 结尾的s字符串中包含多少个以j-1结尾的t字符串
- 递推公式
if(s.charAt(i-1) == t.charAt(j-1)){
dp[i][j] = dp[i-1][j-1] + dp[i-1][j]; // 选择使用s[i-1] + 选择不使用s[i-1]的次数加起来
}else{
dp[i][j] = dp[i-1][j]; // 不相等的话只能依靠前面的
}
设置i-1, j-1 是为了初始化的时候更加方便。
class Solution {
public int numDistinct(String s, String t) {
int len1 = s.length(), len2 = t.length();
int[][] dp = new int[len1+1][len2+1];
// dp[i][j] 表示以i-1 结尾的s字符串中包含多少个以j-1结尾的t字符串
for(int i = 0; i<=len1; i++){
dp[i][0] = 1;
}
for(int i = 1; i <= len1; i++){
for(int j = 1; j <= len2; j++){
if(s.charAt(i-1) == t.charAt(j-1)){
dp[i][j] = dp[i-1][j-1] + dp[i-1][j]; // 选择使用s[i-1] + 选择不使用s[i-1]的次数加起来
}else{
dp[i][j] = dp[i-1][j];
}
}
// System.out.println(Arrays.toString(dp[i]));
}
return dp[len1][len2];
}
}
Leetcode 583. 两个字符串的删除操作
题目链接
题目说明
给定两个单词 word1 和 word2 ,返回使得 word1 和 word2 相同所需的最小步数。
每步 可以删除任意一个字符串中的一个字符。
代码说明
dp数组含义
dp[i][j]:以i-1为结尾的字符串word1,和以j-1位结尾的字符串word2,想要达到相等,所需要删除元素的最少次数。
递推公式
当word1[i - 1] 与 word2[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, 情况三的结果会等于情况1
这题难在初始化时, dp[i][0] = i 而不是1
如果相等, 说明不需要删除, dp[i][j] = dp[i-1][j-1], 如果不相等, 则取小的最小删除数再加1.
class Solution {
public int minDistance(String word1, String word2) {
int len1 = word1.length(), len2 = word2.length();
int[][] dp = new int[len1+1][len2+1];
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(word1.charAt(i-1) == word2.charAt(j-1)){
dp[i][j] = dp[i-1][j-1];
}else{
dp[i][j] = Math.min(dp[i][j-1], dp[i-1][j]) + 1;
}
}
// System.out.println(Arrays.toString(dp[i]));
}
return dp[len1][len2];
}
}
Leetcode 72. 编辑距离
题目链接
题目说明
给你两个单词 word1 和 word2, 请返回将 word1 转换成 word2 所使用的最少操作数 。
你可以对一个单词进行如下三种操作:
插入一个字符
删除一个字符
替换一个字符
代码说明
将上面那题弄懂之后, 难题也变简单了, 就是增加了一个表达式的
dp[i][j] = Math.min(dp[i][j-1], Math.min(dp[i-1][j], dp[i-1][j-1])) + 1;
以前需要删除两下的操作, 我可以通过替换一步就可以做出来。
情况三由:同时删word1[i - 1]和word2[j - 1],操作的最少次数为dp[i - 1][j - 1] + 2
变成了dp[i-1][j-1] + 1 的替换操作
class Solution {
public int minDistance(String word1, String word2) {
int len1 = word1.length(), len2 = word2.length();
int[][] dp = new int[len1+1][len2+1];
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(word1.charAt(i-1) == word2.charAt(j-1)){
dp[i][j] = dp[i-1][j-1];
}else{
dp[i][j] = Math.min(dp[i][j-1], Math.min(dp[i-1][j], dp[i-1][j-1])) + 1;
}
}
// System.out.println(Arrays.toString(dp[i]));
}
return dp[len1][len2];
}
}