1. Edit Distance
问题描述
Given two words word1 and word2, find the minimum number of operations required to convert word1 to word2.
You have the following 3 operations permitted on a word:
- Insert a character
- Delete a character
- Replace a character
Example 1:
Input: word1 = "horse", word2 = "ros"
Output: 3
Explanation:
horse -> rorse (replace 'h' with 'r')
rorse -> rose (remove 'r')
rose -> ros (remove 'e')
Example 2:
Input: word1 = "intention", word2 = "execution"
Output: 5
Explanation:
intention -> inention (remove 't')
inention -> enention (replace 'i' with 'e')
enention -> exention (replace 'n' with 'x')
exention -> exection (replace 'n' with 'c')
exection -> execution (insert 'u')
思路
这道题的中文意思是,给我们两个字符串word1
和word2
,想让我们计算出最少通过几步修改(增、删、改一个字符)可以将 word1 变成 word2 。一开始看起来很复杂,因为从 word1 变到 word2 有很多种修改的情况。但其实是增、删还是改,改动的是什么字符,其实都对这道题的求解没有影响,只要知道改动的最小字符数即可。我们只需要用动态规划,先求每个子问题(子串)改动的最少字符数,最后一步一步推到更大的问题即可。
假设 word1 = “horse”, word2=“ros”,
要求 horse 最少需要经过几次修改能变成 ros ,我们可以先求 word1[1:j] 最少需要经过几次可以变到 word2[1:i]
可以画出下面的表,在代码里可以用二维数组table来表示下表
word2 \ word1 | 空 | h | o | r | s | e |
---|---|---|---|---|---|---|
空 | 0 | 1 | 2 | 3 | 4 | 5 |
r | 1 | 1 | 2 | 2 | 3 | 4 |
o | 2 | 2 | 1 | 2 | 3 | 4 |
s | 3 | 3 | 2 | 2 | 2 | 3 |
上表增加了空串的行和列,这是我们的初始状态。
若 word1 为空串,则它转到空串 word2 需要0次修改,转到 “r” 需要 1 次修改,转到 “ro” 需要 2 次修改,转到 “ros” 需要 3 次修改。我们可以在一开始就直接对 table[j][0] 进行赋值。同样的道理,若 word1 为 “”,转到 word2 = “” 需要 0 次修改,“h” 转到 “” 需要 1 次修改,“ho” 转到 “” 需要 2 次修改,“hor” 转到 “” 需要 3 次修改,“hors” 转到 “” 需要 4 次修改,“horse” 转到 “” 需要 5 次修改。我们也是在一开始就直接对 table[0][i] 赋值。
for(int i = 0; i <= src_len; i++){
table[0][i] = i;
}
for(int j = 0; j <= target_len; j++){
table[j][0] = j;
}
有了初值之后,我们就要考虑如何进行状态转移了。table[i][j] 有三个来源:
① 斜上方 table[i-1][j-1]
。
例如 sub_word1 = “hor”, sub_word2 = “ros”,table[i-1][j-1] = 1 表示的是 sub_word1 = “ho” 和 sub_word2 = “ro” 只需要修改 1 个字母即可,即把 h -> r。而 “hor”->“ros” 可以看成 “ho”->“ro” + “r”->“s”,这样修改的步数即为 table[i-1][j-1]+1 = 2。要注意如果 word1[j] == word2[i] 的话,例如 sub_word1 = “hor”, sub_word2=“ror”, 则这个时候 table[i][j] = table[i-1][j-1],因为我们不用对最后一个字母进行处理,所以 “hor” -> “ror” 其实就等价于 “ho” -> “ro”。
②左方table[i][j-1]
。
还是以 sub_word1 = “hor”, sub_word2 = “ros” 为例,“ho”->“ros” 的最小修改操作次数为2次,其中一种为 “ho”->“ro”, “ro”->“ros”。所以 “hor”->“ros” 可以由 “hor”->“ror”, “ror”->“rosr”, “rosr”->“ros” 得到,这时需要修改 3 次,这个时候 table[i][j] = table[i][j-1]+1。
③上方table[i-1][j]
同样以 sub_word1 = “hor”, sub_word2 = “ros” 为例,“hor”->“ro” 的最小修改操作次数为 2 次,其中一种为 “hor”->“ror”,“ror”->“ro”。所以 “hor”->“ros” 可以由 “hor”->“ror”, “ror”->“ro”, “ro”->“ros” 得到,这时需要修改 3 次,这个时候 table[i][j] = table[i-1][j]+1。
由于我们需要找的是最小的修改次数,所以只需要求出三个值中最小的那个,赋给 table[i][j] 即可。
if(word1[j-1] == word2[i-1]){ //如果字母相等,则不用修改,直接用table[i-1][j-1]的值就行了
table[i][j] = minNum(table[i-1][j-1], table[i][j-1]+1, table[i-1][j]+1);
}
else{
table[i][j] = minNum(table[i-1][j-1]+1, table[i][j-1]+1, table[i-1][j]+1);
}
代码
class Solution {
public:
int minNum(int a, int b, int c){
int min_tmp = min(a, b);
int min_result = min(min_tmp, c);
return min_result;
}
int minDistance(string word1, string word2) {
int src_len = word1.size();
int target_len = word2.size();
vector<vector<int>> table(target_len+1, vector<int>(src_len+1, 0));
// 横向 赋初值
for(int i = 0; i <= src_len; i++){
table[0][i] = i;
}
// 纵向 赋初值
for(int j = 0; j <= target_len; j++){
table[j][0] = j;
}
// 开始计算
for(int i = 1; i <= target_len; i++){
for(int j = 1; j <= src_len; j++){
if(word1[j-1] == word2[i-1]){ //如果字母相等,则不用修改,直接用table[i-1][j-1]的值就行了
table[i][j] = minNum(table[i-1][j-1], table[i][j-1]+1, table[i-1][j]+1);
}
else{
table[i][j] = minNum(table[i-1][j-1]+1, table[i][j-1]+1, table[i-1][j]+1);
}
}
}
return table[target_len][src_len];
}
};
2. Distinct Subsequences
问题描述
Given a string S and a string T, count the number of distinct subsequences of S which equals T.
A subsequence of a string is a new string which is formed from the original string by deleting some (can be none) of the characters without disturbing the relative positions of the remaining characters. (ie, "ACE"
is a subsequence of "ABCDE"
while "AEC"
is not).
Example 1:
Input: S = "rabbbit", T = "rabbit"
Output: 3
Explanation:
As shown below, there are 3 ways you can generate "rabbit" from S.
(The caret symbol ^ means the chosen letters)
rabbbit
^^^^ ^^
rabbbit
^^ ^^^^
rabbbit
^^^ ^^^
Example 2:
Input: S = "babgbag", T = "bag"
Output: 5
Explanation:
As shown below, there are 5 ways you can generate "bag" from S.
(The caret symbol ^ means the chosen letters)
babgbag
^^ ^
babgbag
^^ ^
babgbag
^ ^^
babgbag
^ ^^
babgbag
^^^
思路
这道题给我们两个字符串 S
和 T
,要我们求出 S 中有多少个子序列可以构成 T 。这道题其实和上面一道题用的方法是一样的,都是使用动态规划,从小问题推到大问题。
假设 S = “babgbag”, T = “bag”,
要求 babgbag 有多少个子序列为 bag ,我们可以先求 S[1:j] 里面有多少个子序列为 T[1:i]
可以画出下面的表,在代码里可以用二维数组 table 来表示下表
T\S | 空 | b | a | b | g | b | a | g |
---|---|---|---|---|---|---|---|---|
空 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
b | 0 | 1 | 1 | 2 | 2 | 3 | 3 | 3 |
a | 0 | 0 | 1 | 1 | 1 | 1 | 4 | 4 |
g | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 5 |
上表增加了空串的行和列,作为初始状态。
① S 为空串。若 T 为空串,则 S 中有一个子序列可以构成 T 。若 T 为 b,ba,bag,则这时空串 S 无法构成 T,所以对应的 table 值为 0 。table[j][0] = 0, table[0][0] = 1
.
② T 为空串。若 S 也为空串,则 S 中有一个子序列可以构成 T。若 S 为 b, ba, bab. babg, babgb, babgba. babgbag, 则 S 都可以有一个子序列(空串)构成 T,所以对应的 table 值为 1。table[0][i] = 1
。
for(int i = 0; i <= t_len; i++){
table[i][0] = 0;
}
for(int j = 0; j <= s_len; j++){
table[0][j] = 1;
}
对于任意一个 table[i][j],可能有两种情况:
① S[j] == T[i],则 table[i][j] = table[i-1][j-1] + table[i][j-1]
以 sub_S = “babgba”, sub_T = “ba” 为例,可以看到两个串最后一个字符是一样的,所以 “babgb” 中可以构成 “b" 的子序列后面加上 a 就变成了可以构成 “ba” 的子序列了。除了这一部分的子序列, 还有另一部分的子序列来自”babgb“本身就可以构成”ba“的子序列(而不需要添加a)。所以 table[i][j] = table[i-1][j-1] + table[i][j-1]。
② S[j] != T[i],则 table[i][j] = table[i][j-1]
。
以 sub_S = “ba”, sub_T = “b” 为例,可以看到两个串最后一个字符是不一样的,ba 可以构成 b 的子序列和 b 可以构成 b 的子序列的数量是一样的。所以有 table[i][j] = table[i][j-1]
for(int i = 1; i <= t_len; i++){
for(int j = 1; j <= s_len; j++){
// 判断s[j]和t[i]是否相等
// 若相等,则说明table[i-1][j-1]里面的情况在增加了一个相同字母的情况下也是符合的
// 以及还有之前j-1 match target的那些也是符合的
if(s[j-1] == t[i-1]){ //我们用的是加过1的 所以这里要减去1
table[i][j] = table[i-1][j-1] + table[i][j-1];
cout << table[i][j] << endl;
}
else{
table[i][j] = table[i][j-1];
cout << table[i][j] << endl;
}
}
}
代码
class Solution {
public:
int numDistinct(string s, string t) {
int s_len = s.size();
int t_len = t.size();
vector<vector<int>> table(t_len+1, vector<int>(s_len+1));
// 先赋空串初始值
// 竖
for(int i = 0; i <= t_len; i++){
table[i][0] = 0;
}
// 横
for(int j = 0; j <= s_len; j++){
table[0][j] = 1;
}
for(int i = 1; i <= t_len; i++){
for(int j = 1; j <= s_len; j++){
// 判断s[j]和t[i]是否相等
// 若相等,则说明table[i-1][j-1]里面的情况在增加了一个相同字母的情况下也是符合的
// 以及还有之前j-1 match target的那些也是符合的
// i,j是从1开始的,相当于下标加了1,所以判断当前两个数字是否相等要将i,j减去1
if(s[j-1] == t[i-1]){
table[i][j] = table[i-1][j-1] + table[i][j-1];
}
else{
table[i][j] = table[i][j-1];
}
}
}
return table[t_len][s_len];
}
};