当前位置: 首页 > news >正文

❤️独特的算法❤️:一文解决编辑距离问题

编辑距离问题

题目关键点
115. 不同的子序列 - 力扣(LeetCode)*dp数组定义,情况讨论
583. 两个字符串的删除操作 - 力扣(LeetCode)两个字符串删除,情况讨论多加一种
72. 编辑距离 - 力扣(LeetCode)删除 == 添加 、替换操作?
  • 115. 不同的子序列 - 力扣(LeetCode)

    1. 确定dp数组(dp table)以及下标的含义

      dp[i][j]以i-1为结尾的s子序列中出现以j-1为结尾的t的个数dp[i][j]

      这样定义,注定s中要删除元素,满足t的条件。比如s:bagg,t:bag,那么就需要s中删除元素满足t的条件。

    本题刚开始的dp数组定义就与之前子序列的定义不同,所以分析方法也不同。

    1. 确定递推公式:这一类问题,基本是要分析两种情况

      • s[i - 1] 与 t[j - 1]相等时,dp[i][j]可以有两部分组成。

        • 一部分是用s[i - 1]来匹配,那么个数不变,还是看上一个序列的个数dp[i - 1][j - 1]。-
        • 一部分是不用s[i - 1]来匹配,个数为dp[i - 1][j]。因为s序列中可能出现重复的部分。

        例如: s:bagg 和 t:bag ,s[3] 和 t[2]是相同的,但是字符串s也可以不用s[3]来匹配,即用s[0]s[1]s[2]组成的bag。当然也可以用s[3]来匹配,即:s[0]s[1]s[3]组成的bag。

      • s[i - 1] 与 t[j - 1] 不相等

        • dp[i][j]只有一部分组成,不用s[i - 1]来匹配(就是模拟在s中删除这个元素),即:dp[i - 1][j]

        所以递推公式为:dp[i][j] = dp[i - 1][j];

  1. dp数组如何初始化

    从递推公式dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j]; 和 dp[i][j] = dp[i - 1][j]; 中可以看出dp[i][j] 是从上方和左上方推导而来,那么dp[i][0]dp[0][j]是一定要初始化的。

    • dp[i][0] 表示:以i-1为结尾的s可以随便删除元素,出现空字符串的个数。那么dp[i][0]一定都是1,因为把以i-1为结尾的s,删除所有元素,出现空字符串的个数就是1。

    • dp[0][j]:空字符串s可以随便删除元素,出现以j-1为结尾的字符串t的个数。那么dp[0][j]一定都是0,s如论如何也变成不了t。

    • dp[0][0]应该是1,空字符串s,可以删除0个元素,变成空字符串t。

  2. 遍历的时候一定是从上到下,从左到右,这样保证dp[i][j]可以根据之前计算出来的数值进行计算。

    class Solution {public int numDistinct(String s, String t) {int m = s.length();int n = t.length();int [][] dp = new int [m + 1][n + 1];//dp数组的初始化for(int i = 1 ; i <= m ; i ++){dp[i][0] = 1;}for(int i = 1 ; i <= n ; i ++){dp[0][i] = 0;}dp[0][0] = 1;for(int i = 1 ; i <= m ; i ++){char s1 = s.charAt(i - 1);for(int j = 1 ; j <= n ; j ++){char t1 = t.charAt(j - 1);//s1 == t1 存在两种情况,不用s[i - 1]匹配 + 用s[i - 1]匹配if(s1 == t1) dp[i][j] = dp[i - 1][j] + dp[i - 1][j - 1];//s1 != t1 只有一种情况,不用s[i - 1]匹配。else dp[i][j] = dp[i - 1][j];// System.out.println("以s[" + (i - 1) + "]结尾的字符串中,以t[" + (j - 1) +"]结尾的子序列的个数为" + dp[i][j]);}}return dp[m][n];}
    }
    
  • 583. 两个字符串的删除操作 - 力扣(LeetCode)

    1. dp定义:dp[i][j]:以i - 1结尾的word1和以j - 1结尾的word2,删除字符后使两个单词相等的最小删除步数为dp[i][j]

    2. dp数组推导:

      word1[i - 1] = word2[j - 1]:不需要删除:dp[i][j] = dp[i - 1][j - 1]

      word1[i - 1] != word2[j - 1]:需要删除:删除word1或删除word2dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1)

      dp[i - 1][j],此时dp数组的定义为以i - 2结尾的word1和以j - 1结尾的word2,删除字符后使两个单词相等的最小删除步数。

      相当于从dp数组定义上删除了i - 1这个字符。

    3. 初始化:dp[i][0]:word2为空字符串,以i-1为结尾的字符串word1要删除多少个元素,才能和word2相同呢,很明显dp[i][0] = idp[0][j]的话同理。

    4. 遍历顺序从前往后,从上往下遍历。

    5. 举例推导dp

      class Solution {public int minDistance(String word1, String word2) {int m = word1.length();int n = word2.length();int [] [] dp = new int [m + 1][n + 1];for(int i = 0 ; i <= m ; i ++){dp[i][0] = i;}for(int j = 0 ; j <= n ; j ++){dp[0][j] = j;}for(int i = 1 ; i <= m ; i ++){char w1 = word1.charAt(i - 1);for(int j = 1 ; j <= n ; j ++){char w2 = word2.charAt(j - 1);if(w1 == w2) dp[i][j] = dp[i - 1][j - 1];else dp[i][j] = Math.min(dp[i - 1][j] + 1 , dp[i][j - 1] + 1);//System.out.println("以word1[" + (i - 1) + "]和word[" + (j - 1) + "]结尾的单词,最少需要" + dp[i][j] + "步删除才能使word1与word2相等");}}return dp[m][n];}
      }
      
  • 72. 编辑距离 - 力扣(LeetCode)

    1. dp[i][j]:以i - 1结尾的word1和以j - 1结尾的word2,转换所需的最小操作数为dp[i][j]

    2. word1[i - 1] == word2[j - 1] :不需要进行操作,dp[i][j] = dp[i - 1][j - 1]

      word1[i - 1] != word2[j - 1]:需要进行操作:

      删除(添加):word2删除一个元素,相当于word1添加一个元素。

      word1删除一个元素:dp[i][j] = dp[i - 1][j] + 1

      word2删除一个元素(word1添加元素):dp[i][j] = dp[i][j - 1] + 1

      替换:可以回顾一下,if (word1[i - 1] == word2[j - 1])的时候我们的操作 是 dp[i][j] = dp[i - 1][j - 1] 对吧。

      那么只需要一次替换的操作,就可以让 word1[i - 1] 和 word2[j - 1] 相同。所以 dp[i][j] = dp[i - 1][j - 1] + 1;

      这里的替换操作不需要考虑具体细节,只需要想,替换操作就是把不同的数替换为相同的数,比相同时的操作要多一步。

    3. dp数组初始化:dp[i][0] :以下标i-1为结尾的字符串word1,和空字符串word2,最近编辑距离为dp[i][0]

      那么dp[i][0]就应该是i,对word1里的元素全部做删除操作,即:dp[i][0] = i;

      同理dp[0][j] = j;

    4. 从上往下,从左往右遍历。

    5. 举例推导dp数组

      class Solution {public int minDistance(String word1, String word2) {int m = word1.length();int n = word2.length();int [] [] dp = new int [m + 1][n + 1];for(int i = 0 ; i <= m ; i ++){dp[i][0] = i;}for(int j = 0 ; j <= n ; j ++){dp[0][j] = j;}for(int i = 1 ; i <= m ; i ++){char w1 = word1.charAt(i - 1);for(int j = 1 ; j <= n ; j ++){char w2 = word2.charAt(j - 1);if(w1 == w2) dp[i][j] = dp[i - 1][j - 1];else dp[i][j] = Math.min(dp[i - 1][j - 1] + 1 , Math.min(dp[i - 1][j] + 1 , dp[i][j - 1] + 1));//System.out.println("以word1[" + (i - 1) + "]和word2[" + (j - 1) + "]结尾的单词,word1最少需要" + dp[i][j] + "步操作才能使word1与word2相等");}}return dp[m][n];}
      }
      

总结

  • 392. 判断子序列 - 力扣(LeetCode)对比1143T,1143是两个字符串都可以删元素,而本题如果删元素是删除字符串t,因为只有t有多余的字符串。
  • 115. 不同的子序列 - 力扣(LeetCode),与392. 判断子序列 - 力扣(LeetCode)类似,也是删除元素,并且只能删除其中有多余字符的字符串。不同的是,在s[i - 1]与t[i - 1]相等时,也要考虑不使用s[i - 1]的情况。
  • 583. 两个字符串的删除操作 - 力扣(LeetCode)与1143题思路基本一致。1143题的本质也是删除字符串。
  • 72. 编辑距离 - 力扣(LeetCode)比起删除,多了一步替换的操作,根据word1[i - 1] == word2[j - 1]推导而来,很巧妙。
http://www.lryc.cn/news/43753.html

相关文章:

  • 三次样条样条:Bézier样条和Hermite样条
  • Redis面试题 (2023最新版)
  • 基于springboot实现家乡特色食品景点推荐系统【源码+论文】分享
  • Spring MVC 启动之 HandlerMapping
  • 基于YOLOv5的停车位检测系统(清新UI+深度学习+训练数据集)
  • 【Linux系统编程】5.vim基本操作命令
  • 主流机器学习平台调研与对比分析
  • 作业帮基于明道云开展的硬件业务数字化建设
  • 位图及布隆过滤器的模拟实现与面试题
  • 在 Python 中将天数添加到日期
  • vue3知识点
  • 一行代码生成Tableau可视化图表
  • 链表——删除元素或插入元素(头插法及尾插法)
  • oracle容器的使用
  • 基于springboot会员制医疗预约服务管理信息系统演示【附项目源码】
  • GoogleAdsense国内加载慢怎么解决?
  • 【MySQL专题】03、性能优化之读写分离(MaxScale)
  • Redis7高级之BigKey(二)
  • flex弹性盒子
  • [Java Web]Cookie | 一文详细介绍会话跟踪技术中的Cookie
  • 这可能是2023最全的Java面试八股文,共计1658页,Java技术手册的天花板
  • 字节流及存放本地文件上传和下载文件
  • 【翻译】下一步:Go 泛型
  • 如何简单实现ELT?
  • 细思极恐,第三方跟踪器正在获取你的数据,如何防范?
  • Java基础之==,equal的区别(温故而知新)-----点点滴滴的积累
  • SpringBoot项目使用切面编程实现数据权限管理
  • 亚马逊测评是做什么的,风险有哪些?
  • 安科瑞导轨式智能通讯管理机
  • vs2010下 转换到 COFF 期间失败: 文件无效或损坏