代碼隨想錄算法訓練營|第五十八天|583. 两个字符串的删除操作、72. 编辑距离、编辑距离总结篇。刷题心得(c++)

本文主要是介绍代碼隨想錄算法訓練營|第五十八天|583. 两个字符串的删除操作、72. 编辑距离、编辑距离总结篇。刷题心得(c++),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

讀題

583. 两个字符串的删除操作

自己看到题目的第一想法

看完代码随想录之后的想法

72. 编辑距离

看完代码随想录之后的想法

583. 两个字符串的删除操作 - 實作

思路

代碼隨想錄思路

Code

72. 编辑距离 - 實作

思路

Code

编辑距离总结篇

判斷子序列

不同的子序列

兩個字符串的刪除操作

編輯距離

總結

判斷子序列

不同子序列

兩個字符串的刪除操作

編輯距離


讀題

583. 两个字符串的删除操作

自己看到题目的第一想法

如果今天可以兩個都刪除,那在w1[i - 1] 以及 w2[j - 1]的狀況下有三種狀況,兩者匹配,dp[i - 1][j - 1],不使用w1[i - 1]→dp[i - 1][j] 不使用w2[j - 1] → dp[i][j - 1],有用畫圖的方式嘗試理解但仍然無法推出狀態的改變。

看完代码随想录之后的想法

看完之後,才真正了解自己哪裡錯了,首先下標就定義錯了,下標應該是dp[i][j] w1[i - 1]為結尾以及w2[j - 1]為結尾如果想要達到相等,所需要刪除元素的最少次數為dp[i][j]

根據這個定義,如果相等的時候,不用刪除元素,那就會等於不包含當前i - 1 j - 1的 i - 2 j - 2,也就是dp[i][j] = dp[i - 1][j - 1]

如果不相等,則會有三種狀況可以刪除w1 最少操作次數就是不包含當前的w[i - 1]的狀況也就是dp[i - 1][j] + 1 也可以選擇刪除w2 最少操作次數是dp[i][j - 1] + 1,不包含當前w2[j - 1]的最少狀況。

也可以兩個都刪除,也就是dp[i - 1][j - 1] + 2,但這個我們可以思考為,之前的兩種狀況就有包含了,如果刪除w1,那是取不包含w1[i - 1]的狀況,換言之,就是刪除w1[i - 1]的狀況是dp[i - 1][j] ,在這個基礎上再減去w2[j - 1]這一個數就是dp[i - 1][j] + 1。

最後可以簡化為dp[i][j] = min(dp[i - 1][j] + 1, dp[i][j - 1] + 1);

72. 编辑距离

看完代码随想录之后的想法

非常清晰,基本上有寫過之前的題目就會知道其中的門道,跟上一題一樣,先定義好dp[i][j]的定義,在下標word1[i - 1]以及下標word2[j-1],使其相同的最小編輯距離為dp[i][j]。

那就一樣會有兩種狀況相同與不相同

相同的話就是dp[i][j] = dp[i - 1][j - 1]代表不需要任何操作,跟上一次不包含word1[i - 1]以及word2[j - 1]的狀況一致

不相同的話要做增刪換

增和刪可以想成同樣一個,操作數都是一樣的,引用代碼隨想錄中提到的例子

word2添加一个元素,相当于word1删除一个元素,例如 word1 = "ad" ,word2 = "a"word1删除元素'd' 和 word2添加一个元素'd',变成word1="a", word2="ad", 最终的操作数是一样! dp数组如下图所示意的:

      0     a                   0     a     d+-----+-----+             +-----+-----+-----+0 |  0  |  1  |           0 |  0  |  1  |  2  |+-----+-----+   ===>      +-----+-----+-----+a |  1  |  0  |           a |  1  |  0  |  1  |+-----+-----+             +-----+-----+-----+d |  2  |  1  |+-----+-----+

替換的話代表只需要替換其中一個數就可以一致,那就會是dp[i][j] = dp[i - 1][j - 1] + 1,代表我只要替換word1 或者word2就可以使得兩者相同。使用dp[i - 1][j - 1]的緣故是,這個數不包含word1、或word2,所以如果只需要替換一個數就可以達成,那就是將這個數加一就好

583. 两个字符串的删除操作 - 實作

思路

  1. 定義DP數組以及下標的含意

    i - 1 下標的word1以及 j - 1 下標的word2,要達到相同的子序列最少需要刪除多少次為dp[i][j]

  2. 遞推公式

    分成兩種狀態相同與不相同

    相同的話代表不用刪除,也就是dp[i - 1][j - 1]即為上一次不包含當前兩個數的狀況

    不相同的話有三種狀況

    • 刪除word1
      • 代表不包含當前word1的狀況在加上一個刪除的個數也就是dp[i - 1][j] + 1
    • 刪除word2
      • 代表不包含當前word2的狀況再加上1個刪除數,也就是dp[i][j - 1] + 1
    • 刪除word1、word2
      • 代表不包含當前word1、word2的狀況加上兩個刪除數,也就是dp[i - 1][j - 1] + 2
      • 但可以換個角度來看,如果刪除word1或word2時,我們要先得知不包含word1或word2的狀況,也就是dp[i - 1][j] 或 dp[i][j - 1] 也就是說這兩個狀態組都代表已經忽略word1 以及word 2時,當前的狀況,那在這個基礎上再加1,也可以理解為在忽略word1的狀況下在刪除word2 就會是將word1、word2都刪除的狀況,也就是dp[i - 1][j] + 1。
  3. 根據遞推公式、題意以及定義,確定DP數組如何初始化

    因為兩者都可以刪除,所以在初始話時,空字符串都是0,因為不用刪除,其他的部分則根據當前的位置,刪除不同數量的word即可以成為空字符串,也就是要刪除i 個或j個字,字符串才會為空

  4. 確定遍歷順序

    因為需要左上角的數據來進行遍歷,所以是由前往後。

代碼隨想錄思路

Code

class Solution {
public:int minDistance(string word1, string word2) {vector<vector<int>> dp (word1.size() + 1, vector<int>(word2.size() + 1, 0));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] + 1, dp[i][j - 1] + 1);}}return dp[word1.size()][word2.size()];}
};

72. 编辑距离 - 實作

思路

  1. 定義DP數組以及下標的含意

    i - 1 下標的word1以及 j - 1 下標的word2,要達到相同的子序列最少需要編輯多少次為dp[i][j]

  2. 遞推公式

    分成兩種狀態相同與不相同

    相同的話代表不用編輯,也就是dp[i - 1][j - 1]即為上一次不包含當前兩個數的狀況

    不相同的話有三種狀況,取最小的

    • 刪除\添加 word1
      • 代表不包含當前word1的狀況在加上一個刪除\添加的個數也就是dp[i - 1][j] + 1
    • 刪除\添加 word2
      • 代表不包含當前word2的狀況再加上1個刪除\添加數,也就是dp[i][j - 1] + 1
    • 替換
      • 代表不包含當前word1、word2的狀況加上一個操作,也就是dp[i - 1][j - 1] + 1
  3. 根據遞推公式、題意以及定義,確定DP數組如何初始化

    當j - 1以及 i - 1的word1、word2需要初始化時,需要刪除i個或j個字符才會等於空字符

  4. 確定遍歷順序

    總共有四個遞推公式

    dp[i][j] = dp[i - 1][j - 1]

    dp[i][j] = dp[i - 1][j - 1] + 1

    dp[i][j] = dp[i - 1][j] + 1

    dp[i][j] = dp[i][j - 1] + 1

    因為需要左上角的數據來進行遍歷,所以是由前往後。

Code

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(min(dp[i - 1][j], dp[i][j - 1]), dp[i - 1][j - 1]) + 1;}}return dp[word1.size()][word2.size()];}
};

编辑距离总结篇

判斷子序列

定義: dp[i][j] 代表 i - 1 的s 和 j - 1 的t 相同子序列的長度為dp[i][j]

根據這個定義,我們可以知道當兩者相同時,長度要加一

如果不相等時,則可以視為忽略當下的t[j - 1]取t的前一個數也就是t[j - 2] → dp[i][j] = dp[i][j - 1]

不同的子序列

定義: dp[i][j]: 以i - 1為結尾s子序列當中,出現以j - 1為結尾t的個數為dp[i][j]

根據定義

  • 兩者相等時有兩種狀況

    我可以使用s[i - 1]也可以不使用s[i - 1] 因為定義是i - 1為結尾的s子序列中,出現以j - 1為結尾的t個數

    所以在使用s[i - 1]的狀況,我不用刪除任何元素,所以我的值會是dp[i - 1][j - 1]即上一次的狀況,

    如果不使用s[i - 1]的狀況,我等同於只需要不考慮s[i - 1] 但t[j - 1]仍然在,所以值會是dp[i - 1][j]

    相同時的轉移方程就會是dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];

  • 兩者不相等時相當於s 要刪除元素,因為 s的字符串中,這個位置並沒有t的字串,所以要刪除

    如果是s要刪除元素,那就是取s[i - 2] 這個不包含s[ i -1]的最大值,但t是要比較的子序列,所以t不用動,也就是說這個dp[i][j]會是由s[i - 2]t[j - 1]所組成,所對應的dp數組是dp[i][j] = dp[i - 1][j]。

初始化:

dp[i][0] 一定都是1,因為把s全部刪除後出現空字符的個數就是一

dp[0][j] 因為s無論如何都無法變成t,所以都是0

dp[0][0] 空字符串s可以刪除0個元素變成空字符串t,所以等於1

兩個字符串的刪除操作

定義: i - 1 下標的word1以及 j - 1 下標的word2,要達到相同的子序列最少需要刪除多少次為dp[i][j]

根據這個定義轉移方程會有兩個狀況

  • 兩者相同

相同的話代表不用刪除,也就是dp[i - 1][j - 1]即為上一次不包含當前兩個數的狀況

  • 兩者不相同

不相同的話有三種狀況

  • 刪除word1
    • 代表不包含當前word1的狀況在加上一個刪除的個數也就是dp[i - 1][j] + 1
  • 刪除word2
    • 代表不包含當前word2的狀況再加上1個刪除數,也就是dp[i][j - 1] + 1
  • 刪除word1、word2
    • 代表要不包含word1以及word2的值,也就是dp[i - 1][j - 1] ,並加上刪除兩次,所以是dp[i - 1][j - 1] + 2
    • 但也可以想成取word1不存在的部分也就是dp[i - 1][j],並刪除word2,也就是dp[i - 1][j] + 1,反之也可以想成word2不存在。
  • 那因為我們要找出最少需要刪除多少次,所以就是取刪除狀況中最小的數值+1

狀態轉移方程就會是 dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + 1;

初始化的部分就是要如何將i - 1結尾的word1以及j - 1結尾的word2 刪除至空字符串,就會需要i, j 次的刪除次數才行。

編輯距離

定義: i - 1 下標的word1以及 j - 1 下標的word2,要達到相同的子序列最少需要操作多少次為dp[i][j]

這一題其實就是前面的部分,只是要想明白相同時要做甚麼、不相同時要做甚麼

如果相同,則不操作

不相同則需要找出最小的增、刪、替換的方案

增加跟刪除可以想成同一個操作數

  • 需要增刪word1時,都是取dp[i - 1][j] + 1
  • 需要增刪word2時,都是取dp[i][j - 1] + 1
  • 至於增刪word1、word2則跟之前一樣,可以簡化成上述的兩個式子
  • 需要替換word1或word2的話,則需要取這兩個都不存在的部分加上一次操作數也就是dp[i - 1][j - 1] + 1

所以得出四個轉移方程

if(word1[i - 1] == word2[j - 1])

dp[i][j] = dp[i - 1][j - 1];

else

dp[i][j] = min(min(dp[i - 1][j], dp[i][j - 1]), dp[i - 1][j - 1]) + 1;

總結


在編輯距離的題目當中,有個很重要的核心就是定義好dp[i][j]的定義,在根據這個定義,去推導出公式以及初始化的方式

其實前三題很重要的思維就是對於刪除的理解,在不同的定義上刪除的做法都不太一樣

判斷子序列

刪除是dp[i][j - 1],忽略前一個t

不同子序列

使用s[i - 1]的,我不用刪除任何元素,所以值會是dp[i - 1][j - 1]即上一次的狀況,

如果不使用s[i - 1]的狀況,我等同於只需要不考慮s[i - 1] 但t[j - 1]仍然在,所以值會是dp[i - 1][j]

相同時的轉移方程就會是dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];

兩者不相等時相當於s 要刪除元素,因為 s的字符串中,這個位置並沒有t的字串,所以要刪除

如果是s要刪除元素,那就是取s[i - 2] 這個不包含s[ i -1]的最大值,但t是要比較的子序列,所以t不用動,也就是說這個dp[i][j]會是由s[i - 2]t[j - 1]所組成,所對應的dp數組是dp[i][j] = dp[i - 1][j]。

兩個字符串的刪除操作

  • 刪除word1
    • 代表不包含當前word1的狀況在加上一個刪除的個數也就是dp[i - 1][j] + 1
  • 刪除word2
    • 代表不包含當前word2的狀況再加上1個刪除數,也就是dp[i][j - 1] + 1
  • 刪除word1、word2
    • 代表要不包含word1以及word2的值,也就是dp[i - 1][j - 1] ,並加上刪除兩次,所以是dp[i - 1][j - 1] + 2

編輯距離

所謂的增刪基本上是同一個操作數,只是需要明確甚麼是替換,以及下標定義為何

  • 需要增刪word1時,都是取dp[i - 1][j] + 1
  • 需要增刪word2時,都是取dp[i][j - 1] + 1
  • 需要替換word1或word2的話,則需要取這兩個都不存在的部分加上一次操作數也就是dp[i - 1][j - 1] + 1

整體而言編輯距離透過這幾天的理解過後,透過這次的總結,對於這類題目有更深的了解了。

这篇关于代碼隨想錄算法訓練營|第五十八天|583. 两个字符串的删除操作、72. 编辑距离、编辑距离总结篇。刷题心得(c++)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/332243

相关文章

通俗易懂的Java常见限流算法具体实现

《通俗易懂的Java常见限流算法具体实现》:本文主要介绍Java常见限流算法具体实现的相关资料,包括漏桶算法、令牌桶算法、Nginx限流和Redis+Lua限流的实现原理和具体步骤,并比较了它们的... 目录一、漏桶算法1.漏桶算法的思想和原理2.具体实现二、令牌桶算法1.令牌桶算法流程:2.具体实现2.1

shell脚本自动删除30天以前的文件(最新推荐)

《shell脚本自动删除30天以前的文件(最新推荐)》该文章介绍了如何使用Shell脚本自动删除指定目录下30天以前的文件,并通过crontab设置定时任务,此外,还提供了如何使用Shell脚本删除E... 目录shell脚本自动删除30天以前的文件linux按照日期定时删除elasticsearch索引s

C++使用栈实现括号匹配的代码详解

《C++使用栈实现括号匹配的代码详解》在编程中,括号匹配是一个常见问题,尤其是在处理数学表达式、编译器解析等任务时,栈是一种非常适合处理此类问题的数据结构,能够精确地管理括号的匹配问题,本文将通过C+... 目录引言问题描述代码讲解代码解析栈的状态表示测试总结引言在编程中,括号匹配是一个常见问题,尤其是在

Python调用Orator ORM进行数据库操作

《Python调用OratorORM进行数据库操作》OratorORM是一个功能丰富且灵活的PythonORM库,旨在简化数据库操作,它支持多种数据库并提供了简洁且直观的API,下面我们就... 目录Orator ORM 主要特点安装使用示例总结Orator ORM 是一个功能丰富且灵活的 python O

Java中String字符串使用避坑指南

《Java中String字符串使用避坑指南》Java中的String字符串是我们日常编程中用得最多的类之一,看似简单的String使用,却隐藏着不少“坑”,如果不注意,可能会导致性能问题、意外的错误容... 目录8个避坑点如下:1. 字符串的不可变性:每次修改都创建新对象2. 使用 == 比较字符串,陷阱满

使用C++实现链表元素的反转

《使用C++实现链表元素的反转》反转链表是链表操作中一个经典的问题,也是面试中常见的考题,本文将从思路到实现一步步地讲解如何实现链表的反转,帮助初学者理解这一操作,我们将使用C++代码演示具体实现,同... 目录问题定义思路分析代码实现带头节点的链表代码讲解其他实现方式时间和空间复杂度分析总结问题定义给定

IDEA编译报错“java: 常量字符串过长”的原因及解决方法

《IDEA编译报错“java:常量字符串过长”的原因及解决方法》今天在开发过程中,由于尝试将一个文件的Base64字符串设置为常量,结果导致IDEA编译的时候出现了如下报错java:常量字符串过长,... 目录一、问题描述二、问题原因2.1 理论角度2.2 源码角度三、解决方案解决方案①:StringBui

python使用fastapi实现多语言国际化的操作指南

《python使用fastapi实现多语言国际化的操作指南》本文介绍了使用Python和FastAPI实现多语言国际化的操作指南,包括多语言架构技术栈、翻译管理、前端本地化、语言切换机制以及常见陷阱和... 目录多语言国际化实现指南项目多语言架构技术栈目录结构翻译工作流1. 翻译数据存储2. 翻译生成脚本

C++初始化数组的几种常见方法(简单易懂)

《C++初始化数组的几种常见方法(简单易懂)》本文介绍了C++中数组的初始化方法,包括一维数组和二维数组的初始化,以及用new动态初始化数组,在C++11及以上版本中,还提供了使用std::array... 目录1、初始化一维数组1.1、使用列表初始化(推荐方式)1.2、初始化部分列表1.3、使用std::

C++ Primer 多维数组的使用

《C++Primer多维数组的使用》本文主要介绍了多维数组在C++语言中的定义、初始化、下标引用以及使用范围for语句处理多维数组的方法,具有一定的参考价值,感兴趣的可以了解一下... 目录多维数组多维数组的初始化多维数组的下标引用使用范围for语句处理多维数组指针和多维数组多维数组严格来说,C++语言没