dp:编辑距离

判断子序列

https://leetcode-cn.com/problems/is-subsequence/
给定字符串 s 和 t ,判断 s 是否为 t 的子序列。

思路

此题相当于只有删除操作的编辑距离

  1. dp[i][j] 表示以下标i-1为结尾的字符串s,和以下标j-1为结尾的字符串t,相同子序列的长度为dp[i][j]。
  2. if (s[i - 1] == t[j - 1])
    t中找到了一个字符在s中也出现了
    if (s[i - 1] != t[j - 1])
    相当于t要删除元素,继续匹配
  3. dp[i][j]都是依赖于dp[i - 1][j - 1] 和 dp[i][j - 1],所以dp[0][0]和dp[i][0]是一定要初始化的。
  4. 从前往后
  5. 如果dp[s.size()][t.size()] 与 字符串s的长度相同说明:s与t的最长相同子序列就是s,那么s 就是 t 的子序列。

代码

 public boolean isSubsequence(String s, String t) {
    int[][] dp = new int[s.length() + 1][t.length() + 1];
    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] + 1;
            }else {
                dp[i][j] = dp[i][j - 1];
            }
        }
    }
    if(dp[s.length()][t.length()] == s.length()) return true;
    return false;
}

不同的子序列

https://leetcode-cn.com/problems/distinct-subsequences/
给定一个字符串 s 和一个字符串 t ,计算在 s 的子序列中 t 出现的个数。

思路

  • 当s[i - 1] 与 t[j - 1]相等时,dp[i][j]可以有两部分组成。
    一部分是用s[i - 1]来匹配,那么个数为dp[i - 1][j - 1]。所以s[i-1]与t[j-1]匹配了,即不需要考虑当前s和t的最后一位字母,所以只需要dp[i-1][j-1]
    一部分是不用s[i - 1]来匹配,个数为dp[i - 1][j]。
  • 当s[i - 1] 与 t[j - 1]不相等时,dp[i][j]只有一部分组成,不用s[i - 1]来匹配,即:dp[i - 1][j]
    所以递推公式为:dp[i][j] = dp[i - 1][j];
  • 初始化:dp[i][0] = 1,因为s[i -1 ] 一直不会等于 t[j - 1],于是一直删除s,直到s为空数组,
    空数组的子集也是空数组,所以 = 1;
    dp[0][j]肯定为0,空数组s怎么也不能包含非空的数组t
  • 本题由于每个dp都是通过上一行的dp值来确定的,所以可以使用滚动数组,减少空间消耗,但是要注意 由于每一个dp值都是依赖于他前面的dp值,所以需要倒序遍历 j 从而使 dp 数组的推导是从已知到未知的,不影响初始值

代码

public int numDistinct(String s, String t) {
  int[] dp = new int[t.length() + 1];
   dp[0] = 1;
   for(int i = 1; i <= s.length(); i++) {
       for(int j = t.length(); j >= 1 ; j--) {
           if(s.charAt(i - 1) == t.charAt(j - 1)) {
               dp[j] = dp[j - 1] + dp[j];
           } else {
               dp[j] = dp[j];
           }
       }
   }
   return dp[t.length()];
}

两个字符串的删除操作

https://leetcode-cn.com/problems/delete-operation-for-two-strings/
给定两个单词 word1 和 word2,找到使得 word1 和 word2 相同所需的最小步数,每步可以删除任意一个字符串中的一个字符。

思路

  • 递推公式:
    当word1[i - 1] 与 word2[j - 1]相同的时候
    dp[i][j] = dp[i - 1][j - 1]
    当word1[i - 1] 与 word2[j - 1]不相同的时候
    删word1[i - 1] 操作次数 + 1
    删word2[j - 1] 操作次数 + 1
    同时删word1[i - 1]和word2[j - 1] 操作次数+2
  • 两个子序列之间的dp问题,dp[i][j]通常设置为 i-1,j-1 对应条件的结果,这样做的好处是,方便初始化,方便初始化的原因:
    因为我们对dp数组的遍历的求解过程实质上是从「解决最小的子问题出发 」
    遍历数组转移状态直到 求出「子问题所组成的大问题的结果」,
    我们是能够轻易的得出最小子问题的结果的,所以以dp[0]这个最小的子问题作为初态进行初始化是最方便的
    而dp[i][j]定义为 i-1,j-1 对应条件的结果正是利用了这一点,使我们初始化时总是只需要对0,即对最小的问题判断结果,这正是我们能够解决的最小问题,所以简化了初始化的难度,
    同时也避免了数组的越界问题

代码

public int minDistance(String s1, String s2) {
    int[][] dp = new int[s1.length() + 1][s2.length() + 1];
    for(int i = 0; i <= s1.length(); i++) dp[i][0] = i;
    for(int j = 0; j <= s2.length(); j++) dp[0][j] = j;
    for(int i = 1; i <= s1.length(); i++) {
        for(int j = 1; j <= s2.length(); j++) {
            if(s1.charAt(i - 1) == s2.charAt(j - 1)) {
                dp[i][j] = dp[i - 1][j - 1];
            } else {
                dp[i][j] = Math.min(dp[i - 1][j - 1] + 2, Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1));
            }
        }
    }
    return dp[s1.length()][s2.length()];
}

编辑距离

https://leetcode-cn.com/problems/edit-distance/

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

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

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

思路

  • if (word1[i - 1] == word2[j - 1])
    不操作
  • if (word1[i - 1] != word2[j - 1])


  • if (word1[i - 1] == word2[j - 1]) 那么说明不用任何编辑,dp[i][j] 就应该是 dp[i - 1][j - 1],即dp[i][j] = dp[i - 1][j - 1];
    word1加1,使其word1[i - 1]与word2[j - 1]相同,
    就是以下标i-2为结尾的word1 与 j-1为结尾的word2的最近编辑距离 +1
    word2加1,使其word1[i - 1]与word2[j - 1]相同,
    就是以下标i-1为结尾的word1 与 j-2为结尾的word2的最近编辑距离 +1
    word2加1,相当于word1删1。
    word1加1就相当于多加了一位word1,所以以前依赖的相同字符word1就要少一
    word1减1就相当于多减了一位word1,也就是以前word2中依赖的相同字符少一

代码

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

总结

  • 两个子序列之间的dp问题,dp[i][j]通常设置为 i-1,j-1 对应条件的结果,这样做的好处是,方便初始化
  • 编辑距离问题主要是对两串每个字符相等或不相等的情况分类讨论,还得琢磨琢磨

参考

Carl:https://mp.weixin.qq.com/s/kbs4kCUzg8gPFttF9H3Yyw

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值