Week8

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:

  1. Insert a character
  2. Delete a character
  3. 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')

思路

这道题的中文意思是,给我们两个字符串word1word2,想让我们计算出最少通过几步修改(增、删、改一个字符)可以将 word1 变成 word2 。一开始看起来很复杂,因为从 word1 变到 word2 有很多种修改的情况。但其实是增、删还是改,改动的是什么字符,其实都对这道题的求解没有影响,只要知道改动的最小字符数即可。我们只需要用动态规划,先求每个子问题(子串)改动的最少字符数,最后一步一步推到更大的问题即可。

假设 word1 = “horse”, word2=“ros”,

要求 horse 最少需要经过几次修改能变成 ros ,我们可以先求 word1[1:j] 最少需要经过几次可以变到 word2[1:i]

可以画出下面的表,在代码里可以用二维数组table来表示下表

word2 \ word1horse
012345
r112234
o221234
s332223

上表增加了空串的行和列,这是我们的初始状态

若 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
    ^^^

思路

这道题给我们两个字符串 ST,要我们求出 S 中有多少个子序列可以构成 T 。这道题其实和上面一道题用的方法是一样的,都是使用动态规划,从小问题推到大问题。

假设 S = “babgbag”, T = “bag”,

要求 babgbag 有多少个子序列为 bag ,我们可以先求 S[1:j] 里面有多少个子序列为 T[1:i]

可以画出下面的表,在代码里可以用二维数组 table 来表示下表

T\Sbabgbag
11111111
b01122333
a00111144
g00001115

上表增加了空串的行和列,作为初始状态

① 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];
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值