编辑距离问题操作流程
-
eg: 从 s a t u变为s u 需要经过两次修改或者两次插入或者三次删除操作
-
修改 :从左上方下来 插入:从左边过来 修改 :从上面下来
115.不同的子序列(动态规划,编辑距离问题)
给你两个字符串 s
和 t
,统计并返回在 s
的 子序列 中 t
出现的个数,结果需要对 109 + 7 取模。
class Solution {
public:
int numDistinct(string s, string t) {
//dp为以i-1为末尾的s中有以j-1为末尾的t字符串的个数 = 删除s中的字符 有多少种方法可以得到t
vector<vector<uint64_t>> dp(s.size()+1,vector<uint64_t>(t.size()+1,0));
//初始化
//t为空字符串时 dp的个数
for(int i=0;i<s.size();i++){
dp[i][0]=1;
//dp[0][0]应该是1,空字符串s,可以删除0个元素,变成空字符串t。
}//s为空字符串时 dp的个数
for(int j=1;j<t.size();j++){//所以这里要从1开始
dp[0][j]=0;
}
for(int i=1;i<=s.size();i++){
for(int j=1;j<=t.size();j++){
if(s[i-1]==t[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.size()][t.size()];
}
};
1.dp的含义
dp
是一个二维向量,用于存储动态规划的状态。
-
vector<vector<uint64_t>> dp
:这声明了一个二维向量dp
,其中的元素是uint64_t
类型的。uint64_t
是一个无符号64位整数类型,它可以存储非常大的正整数,这通常用于避免在计算过程中整数溢出。 -
这里
dp[i][j]
将存储字符串s
的前i
个字符中包含字符串t
的前j
个字符作为子序列的不同方式的数量
2.dp数组的初始化
for(int i=0;i<s.size();i++){
dp[i][0]=1;
//dp[0][0]应该是1,空字符串s,可以删除0个元素,变成空字符串t。
}//s为空字符串时 dp的个数
for(int j=1;j<t.size();j++){//所以这里要从1开始
dp[0][j]=0;
}
在初始化dp
数组时,dp[0][j]
代表的是空字符串s
与t
的前j
个字符的匹配方式数量。因为空字符串无法匹配任何非空字符串,所以当t
非空时,dp[0][j]
应该为0。所以,初始化dp[0][j]
为0是正确的。
从1开始初始化的原因是因为dp[0][0]
已经被初始化为1,代表两个空字符串的匹配方式数量。dp[0][0]
是一个特殊情况,代表没有任何字符需要匹配,只有一种方式,即不做任何操作。
对于j
的循环,从1开始是为了跳过dp[0][0]
这个已经初始化的值,从t
的第一个字符开始初始化dp
数组。这样,对于t
中的每个字符,我们都可以将其对应的dp[0][j]
设置为0,因为空字符串s
无法匹配任何t
中的字符。
3.递推方程
if(s[i-1]==t[j-1]){
dp[i][j]=dp[i-1][j-1]+dp[i-1][j];
}else{
dp[i][j]=dp[i-1][j];
}详细解释一下
-
if(s[i-1] == t[j-1])
:这个条件检查s
的第i
个字符(从1开始计数,所以是s[i-1]
)是否等于t
的第j
个字符(同样是t[j-1]
)。如果它们相等,这意味着我们可以使用这个匹配来构建一个有效的子序列。 -
dp[i][j] = dp[i-1][j-1] + dp[i-1][j]
:如果s[i-1]
和t[j-1]
相等,那么dp[i][j]
的值由两部分组成:dp[i-1][j-1]
:这部分代表了当我们不使用s
的第i
个字符时,即只考虑s
的前i-1
个字符时,能够形成t
的前j-1
个字符的子序列的数量。因为我们现在有一个匹配的字符,所以这些子序列现在可以通过添加s[i-1]
来扩展为匹配t
的前j
个字符的子序列。dp[i-1][j]
:这部分代表了当我们使用s
的第i
个字符时,即考虑s
的前i
个字符时,能够形成t
的前j
个字符的子序列的数量。 这部分是在s
的第i
个字符和t
的第j
个字符不匹配时计算的,但是由于我们现在有一个匹配,所以这些子序列也可以通过使用这个匹配来增加数量。
-
else
:如果s[i-1]
和t[j-1]
不相等,那么我们不能使用s
的第i
个字符来构建匹配t
的第j
个字符的子序列。因此,dp[i][j]
的值就等于只考虑s
的前i-1
个字符时(不考虑=删除),能够形成t
的前j
个字符的子序列的数量,即dp[i-1][j]
。
583.两个字符串的删除操作(动态规划,编辑距离问题)
给定两个单词 word1
和 word2
,返回使得 word1
和 word2
相同所需的最小步数。
每步 可以删除任意一个字符串中的一个字符。
示例 1:
输入: word1 = "sea", word2 = "eat" 输出: 2 解释: 第一步将 "sea" 变为 "ea" ,第二步将 "eat "变为 "ea"
示例 2:
输入:word1 = "leetcode", word2 = "etco" 输出:4
class Solution {
public:
int minDistance(string word1, string word2) {
//dp是删除word1或word2所需要的最少步数
vector<vector<int>> dp(word1.size()+1,vector<int>(word2.size()+1));
for(int i=0;i<=word1.size();i++){
dp[i][0]=i;
}
for(int j=0;j<=word2.size();j++){
dp[0][j]=j;
}
for(int i=1;i<=word1.size();i++){
for(int j=1;j<=word2.size();j++){
if(word1[i-1]==word2[j-1]){
dp[i][j]=dp[i-1][j-1];
}else{
//选择删除1字符串和删除2字符串所得到的最小次数
dp[i][j]=min(dp[i-1][j]+1,dp[i][j-1]+1);
}
}
}
return dp[word1.size()][word2.size()];
}
};
1.dp数组的含义
//dp是删除word1或word2所需要的最少步数
2.dp数组的初始化
1. dp[i][0]代表着将字符串 word1[i] 删除成为 空字符串 的最小次数
2. dp[0][j]代表着将字符串 word2[j] 删除成为 空字符串 的最小次数
for(int i=0;i<=word1.size();i++){
dp[i][0]=i;
}
for(int j=0;j<=word2.size();j++){
dp[0][j]=j;
}
3.递推方程
两个嵌套的for
循环用于填充动态规划数组dp
,以计算将字符串word1
转换为字符串word2
所需的最小编辑距离。这里的dp[i][j]
表示将word1
的前i
个字符转换为word2
的前j
个字符所需的最小操作次数。
循环中的i
和j
变量分别对应于word1
和word2
的当前字符索引。由于dp
数组的大小比输入字符串的长度大1,因此i
和j
的取值范围是1
到word1.size()
和1
到word2.size()
,包括边界。
循环体中的代码执行以下操作:
-
检查
word1
的第i
个字符(word1[i-1]
)是否等于word2
的第j
个字符(word2[j-1]
)。如果是,则不需要进行任何操作,dp[i][j]
的值将与dp[i-1][j-1]
相同,即当前两个字符之前的子字符串的编辑距离。 -
如果
word1
的第i
个字符不等于word2
的第j
个字符,则需要考虑三种可能的操作:插入、删除和替换。在这种情况下,dp[i][j]
的值将是以下三种情况中的最小值加1:- 在
word1
中删除第i
个字符,然后比较word1
的前i-1
个字符和word2
的前j
个字符,即dp[i-1][j] + 1
。 - 在
word1
中插入一个字符以匹配word2
的第j
个字符(向word1插入字符等于删除word2中的字符),然后比较word1
的前i
个字符和word2
的前j-1
个字符,即dp[i][j-1] + 1
。
- 在
for(int i=1;i<=word1.size();i++){
for(int j=1;j<=word2.size();j++){
if(word1[i-1]==word2[j-1]){
dp[i][j]=dp[i-1][j-1];
}else{
dp[i][j]=min(dp[i-1][j]+1,dp[i][j-1]+1);
}
}
}
72.编辑距离(动态规划,编辑距离问题)
给你两个单词 word1
和 word2
, 请返回将 word1
转换成 word2
所使用的最少操作数 。
你可以对一个单词进行如下三种操作:
- 插入一个字符
- 删除一个字符
- 替换一个字符
示例 1:
输入:word1 = "horse", word2 = "ros" 输出:3 解释: horse -> rorse (将 'h' 替换为 'r') rorse -> rose (删除 'r') rose -> ros (删除 'e')
示例 2:
输入:word1 = "intention", word2 = "execution" 输出:5 解释: intention -> inention (删除 't') inention -> enention (将 'i' 替换为 'e') enention -> exention (将 'n' 替换为 'x') exention -> exection (将 'n' 替换为 'c') exection -> execution (插入 'u')
class Solution {
public:
int minDistance(string word1, string word2) {
vector<vector<int>> dp(word1.size() + 1, vector<int>(word2.size() + 1));
for (int i = 0; i <= word1.size(); i++)
dp[i][0] = i;
for (int j = 0; j <= word2.size(); j++)
dp[0][j] = j;
for (int i = 1; i <= word1.size(); i++) {
for (int j = 1; j <= word2.size(); j++) {
if (word1[i - 1] == word2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1];
} else {
dp[i][j] =
min({dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]}) + 1;
}
}
}
return dp[word1.size()][word2.size()];
}
};
dp
是一个二维向量,用于存储最小编辑距离的值。dp[i][j]
表示将word1
的前i
个字符转换为word2
的前j
个字符所需的最小操作次数。
代码的逻辑如下:
-
初始化
dp
数组的第一行和第一列,分别对应于将一个字符串转换为空字符串的情况。这可以通过删除操作来实现,因此dp[i][0]
和dp[0][j]
分别被设置为i
和j
。 -
使用两个嵌套的
for
循环遍历word1
和word2
的字符。如果当前字符相同,则dp[i][j]
的值与dp[i-1][j-1]
相同,因为不需要进行任何操作。 -
如果当前字符不同,则需要考虑插入、删除和替换操作。在这种情况下,
dp[i][j]
的值是dp[i-1][j]
、dp[i][j-1]
和dp[i-1][j-1]
中的最小值加上1。这三种情况分别对应于删除word1
的当前字符、插入word2
的当前字符和替换word1
的当前字符。 -
最终,
dp[word1.size()][word2.size()]
的值将是将整个word1
转换为整个word2
所需的最小编辑距离。