代码随想录算法训练营Day50|动态规划part12

67 阅读7分钟

LeetCode 115 不同的子序列

题目链接:leetcode.cn/problems/di…

文档讲解:programmercarl.com/0115.不同的子序列…

视频讲解:www.bilibili.com/video/BV1fG…

思路

给你两个字符串 s 和 t ,统计并返回在 s 的 子序列 中 t 出现的个数,结果需要对 109 +710^9 + 7 取模。

示例:

输入:s = "rabbbit", t = "rabbit"
输出:3
解释:
如下所示, 有 3 种可以从 s 中得到 "rabbit" 的方案。
rabbbit
^^^^ ^^
rabbbit
^^^ ^^^
rabbbit
^^ ^^^^

本题仍然只需要考虑一种编辑——删除(从s中删除若干字符得到t)

考虑动态规划五部曲:

  1. dp数组及其下标的含义:dp[i][j]表示在以s[i-1]结尾的子序列中以t[j-1]结尾的出现的个数。
  2. 确定递推公式:
    1. 如果s[i-1]==t[j-1]。一部份结果使用s[i-1]t[j-1]的匹配,那么匹配的种类就是dp[i-1][j-1],一部份不使用这个匹配,那么匹配的种类就是dp[i-1][j]。所以dp[i][j] = dp[i-1][j-1] + dp[i-1][j]
    2. 如果s[i-1]!=t[j-1]。一定没法使用s[i-1],所以只能用前面的匹配,即dp[i-1][j]
  3. dp数组如何初始化:
    1. dp[i][0]表示以s[i-1]结尾的字符串通过删除字符,出现空串的个数。那么我们只能删除所有字符,所以初始化为1
    2. dp[0][j]表示空串通过删除字符,出现以t[j-1](j≥1)结尾的t的个数,显然0
    3. dp[0][0]表示空串通过删除字符出现空串的个数。只有一种动作就是不删除。所以是1
  4. 确定遍历顺序:i从小到大遍历,j从小到大遍历。
  5. 举例推导dp数组

解法

class Solution {
	public int numDistinct(String s, String t) {	
		int[][] dp = new int[s.length()+1][t.length()+1];		
		// 初始化dp数组		
		for (int i = 0; i <= s.length(); i++) {		
			dp[i][0] = 1;		
		}		
		for (int i = 1; i <= t.length(); i++) {		
			dp[0][i] = 0;		
		}		
		// 递推		
		for (int i = 1; i <= s.length(); i++) {		
			for (int j = 1; j <= t.length(); 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[s.length()][t.length()];	
	}
}

LeetCode 583 两个字符串的删除操作

题目链接:leetcode.cn/problems/de…

文档讲解:programmercarl.com/0583.两个字符串的…

视频讲解:www.bilibili.com/video/BV1we…

思路

给定两个单词 word1 和 word2 ,返回使得 word1 和  word2 相同所需的最小步数

每步 可以删除任意一个字符串中的一个字符。

本题实质上就是找两个字符串的最长子序列,找到了最长子序列的长度也就能计算出删除的次数。作为编辑距离题目,编辑操作也只有删除,比较简单。

考虑动态规划五部曲:

  1. dp数组及其下标含义:dp[i][j]表示以i-1结尾的word1和以j-1结尾的word2所拥有的最长子序列的长度
  2. 确定递推公式:if(word1[i-1] == word2[j-1]) dp[i][j] = dp[i-1][j-1] + 1否则,dp[i][j] = max(dp[i-1][j], dp[i][j-1])
  3. dp数组如何初始化:dp[i][0]dp[0][j]都初始化为0
  4. 确定遍历顺序:从上到下,从左到右遍历
  5. 举例推导dp数组

解法

class Solution {
	public int minDistance(String word1, String word2) {		
		int[][] dp = new int[word1.length()+1][word2.length()+1];		
		for (int i = 0; i <= word1.length(); i++) {		
			dp[i][0] = 0;		
		}		
		for (int i = 0; i <= word2.length(); i++) {		
			dp[0][i] = 0;		
		}		
		for (int i = 1; i <= word1.length(); i++) {		
			for (int j = 1; j <= word2.length(); j++) {			
				if (word1.charAt(i-1) == word2.charAt(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 word1.length() + word2.length() - dp[word1.length()][word2.length()] * 2;	
	}
}

LeetCode 72 编辑距离

题目链接:leetcode.cn/problems/ed…

文档讲解:programmercarl.com/0072.编辑距离.h…

视频讲解:www.bilibili.com/video/BV1qv…

思路

给你两个单词 word1 和 word2, 请返回将 word1 转换成 word2 所使用的最少操作数  。

你可以对一个单词进行如下三种操作:

  • 插入一个字符
  • 删除一个字符
  • 替换一个字符

考虑动态规划五部曲:

  1. dp数组及其下标的含义:dp[i][j]表示把以i-1结尾的word1转换为以j-1结尾的word2所使用的最少操作数
  2. 确定递推公式:先分为以下两种大情况
    1. word1[i-1]==word2[j-1],说明新增的一对字符不需要操作,dp[i][j]=dp[i-1][j-1]
    2. word1[i-1]!=word2[j-1],对三种编辑操作讨论,取最小值
      1. 如果把word1[i-1]直接替换成word2[j-1],说明需要i-1这个位置和j-1匹配,所以dp[i][j] = 1 + dp[i-1][j-1]
      2. 如果把word1[i-1]直接删除,说明j-1可以经过操作和word1[i-1]之前的字符匹配,所以dp[i][j] = 1 + dp[i-1][j]
      3. 如果是在word1[i-1]后插入word2[j-1],说明word1[i]之前的字符已经和word2[j-2]匹配。所以dp[i][j] = 1 + dp[i][j-1]
  3. dp数组如何初始化:根据数组定义和递推公式,我们需要初始化任一索引为0的元素
    1. dp[i][0]:表示把以i-1结尾的word1转换成空串需要的操作数,只需要删除所有字符。所以初始化为i
    2. dp[0][j]:表示把空串转换成以j-1结尾的word2需要的操作数,只需要添加所有字符。所以初始化为j
  4. 确定遍历顺序:根据递推公式,一个元素以赖于其上、左、斜上方的元素,座椅从左到右从上到下遍历
  5. 举例推导dp数组

解法

class Solution {
	public int minDistance(String word1, String word2) {	
		int[][] dp = new int[word1.length()+1][word2.length()+1];		
		for (int i = 0; i <= word1.length(); i++) {		
			dp[i][0] = i;		
		}		
		for (int i = 0; i <= word2.length(); i++) {		
			dp[0][i] = i;		
		}		
		for (int i = 1; i <= word1.length(); i++) {		
			for (int j = 1; j <= word2.length(); 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]);					
					dp[i][j] = Math.min(dp[i][j], dp[i-1][j-1]);					
					dp[i][j]++;				
				}			
			}		
		}		
		return dp[word1.length()][word2.length()];	
	}
}

编辑距离总结

入门:判断子序列

给定字符串 s 和 t ,判断 s 是否为 t 的子序列。 用编辑距离的定义考虑,就是把t删除为s,编辑距离是否等于s和t的长度之差

  • 定义数组:子序列长度(可以和编辑距离等价转换)
  • 递推思路:讨论s[i-1]t[j-1]是否相同,不相同只能让t删除字符

初级:不同的子序列

给定一个字符串 s 和一个字符串 t ,计算在 s 的子序列中 t 出现的个数。 用编辑距离的定义考虑,就是把s删除成t,有几种编辑方法(几种向量编辑距离)

  • 定义数组:子序列个数
  • 递推思路:讨论s[i-1]t[j-1]是否相同,相同时s[i-1]可以匹配也可以不匹配,不同时只能不匹配

中级:两个字符串的删除操作

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

  • 定义:子序列长度(可以和编辑距离等价转换)
  • 递推思路:讨论word1[i-1]word2[j-1]是否相同,相同时一定匹配,不同时最多使用一个

终极:编辑距离

给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。操作可以是增删改。

  • 定义:编辑距离
  • 递推思路:讨论word1[i-1]word2[j-1]是否相同,相同时匹配,不同时讨论三种操作

今日收获总结

今日学习三小时。总结就是对编辑距离的总结啦。