代码随想录day48|583两个字符串的删除操作72编辑距离|01笔记

112 阅读5分钟
  • 583两个字符串的删除操作

  • 代码随想录 (programmercarl.com)
  • 第一印象

  • 求删除操作的步骤数目,也就相当于求相同的最长子序列长度,然后再用两个word的总长和去减。因为如果相同的子序列如果短一位,那么相当于删除步骤就要增加2步。
  • 讲解观后感

  • 除了使用求得最长公共子序列的方法,我们也可已使用类似115不同子序列中讨论删除情况的方法来解题。
  • 我们按照讨论删除的方法捋顺一遍。
  • 先确定dp数组(dp table)以及下标的含义
    dp[i][j]:以i-1为结尾的字符串word1,和以j-1位结尾的字符串word2,想要达到相等,所需要删除元素的最少次数。
  • 然后确定递推公式。分相等和不相等两种情况
    • 当word1[i - 1] 与 word2[j - 1]相同的时候,dp[i][j] = dp[i - 1][j - 1];
    • 当word1[i - 1] 与 word2[j - 1]不相同的时候,有三种情况:
      情况一:删word1[i - 1],最少操作次数为dp[i - 1][j] + 1
      情况二:删word2[j - 1],最少操作次数为dp[i][j - 1] + 1
      情况三:同时删word1[i - 1]和word2[j - 1],操作的最少次数为dp[i - 1][j - 1] + 2
      取最小dp[i][j] = min({dp[i - 1][j - 1] + 2, dp[i - 1][j] + 1, dp[i][j - 1] + 1})
    • 这里我们可以通过意义得来,dp[i-1][j-1]+2的情况一定是某一个[x-1]通过+1得到的。
      所以递推公式可简化为:dp[i][j] = min(dp[i - 1][j] + 1, dp[i][j - 1] + 1)
  • dp的初始化
    • dp[i][0]:word2为空字符串,以i-1为结尾的字符串word1要删除多少个元素,才能和word2相同呢,很明显dp[i][0] = i。dp[0][j]的话同理
  • 确定遍历的顺序,通过观察状态转移公式。遍历的时候一定是从上到下,从左到右。
  • 解题代码

  • 动态规划删除数
  •     func minDistance(word1 string, word2 string) int {
        	dp := make([][]int, len(word1)+1)
        	for i := 0; i < len(dp); i++ {
        		dp[i] = make([]int, len(word2)+1)
        	}
        	//初始化
        	for i := 0; i < len(dp); i++ {
        		dp[i][0] = i
        	}
        	for j := 0; j < len(dp[0]); j++ {
        		dp[0][j] = j
        	}
        	for i := 1; i < len(dp); i++ {
        		for j := 1; j < len(dp[i]); 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]+1, dp[i][j-1]+1), dp[i-1][j-1]+2)
        			}
        		}
        	}
        	return dp[len(dp)-1][len(dp[0])-1]
        }
        
        func min(a, b int) int {
        	if a < b {
        		return a
        	}
        	return b
        }
    
  • 动态规划保留数
  •     func minDistance(text1 string, text2 string) int {
        	t1 := len(text1)
        	t2 := len(text2)
        	dp:=make([][]int,t1+1)
        	for i:=range dp{
        		dp[i]=make([]int,t2+1)
        	}
        
        	for i := 1; i <= t1; i++ {
        		for j := 1; j <=t2; j++ {
        			if text1[i-1]==text2[j-1]{
        				dp[i][j]=dp[i-1][j-1]+1
        			}else{
        				dp[i][j]=max(dp[i-1][j],dp[i][j-1])
        			}
        		}
        	}
        	return t1 + t2  - dp[t1][t2] * 2  //分别在两个字符串中减去重复的子序列
        }
        
        func max(a,b int)int  {
        	if a>b{
        		return a
        	}
        	return b
        }
    
  • 72编辑距离

  • 代码随想录 (programmercarl.com)
  • 第一印象

  • 结合之前的题目经历。我可以比较方便的想到当匹配到的字符在相同时和不同时,使用删除时的情况。但是在考虑删除或是增加的操作时,我们还要已匹配的长度关系。但是当我们同时使用删除和增加操作时,其实和只使用一种操作在另一个字符串的意义是一样的。
  • 以力扣题目示例来讨论
  •     输入:word1 = "intention", word2 = "execution"
        输出:5
        解释:
        intention -> inention (删除 't')
        inention -> enention (将 'i' 替换为 'e')
        enention -> exention (将 'n' 替换为 'x')
        exention -> exection (将 'n' 替换为 'c')
        exection -> execution (插入 'u')
        
        来源:力扣(LeetCode)
        链接:https://leetcode.cn/problems/edit-distance
        著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
    
  • 我们在'exection'插入‘u’的时候,其实是和在‘execution’删除‘u’是一样的。
  • 所以我们可以利用类似于上面583的方法。
  • 讲解观后感

  • 还是按照动规的固定思考顺序
  • 先确定dp数组(dp table)以及下标的含义。dp[i][j] 表示以下标i-1为结尾的字符串word1,和以下标j-1为结尾的字符串word2,最近编辑距离为dp[i][j]。这样做是为了方便符合意义的初始化。
  • 然后来确定递推公式。首先要考虑清楚编辑的几种操作
  •     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]
  • if (word1[i - 1] != word2[j - 1]) ,此时就需要编辑了:
    • 操作一:word1删除一个元素,那么就是以下标i - 2为结尾的word1 与 j-1为结尾的word2的最近编辑距离再加上一个操作。
      即  dp[i][j] = dp[i - 1][j] + 1
    • 操作二:word2删除一个元素,那么就是以下标i - 1为结尾的word1 与 j-2为结尾的word2的最近编辑距离再加上一个操作。(这里在word2删除一个元素就相当于在word1增加一个元素的操作)
      即  dp[i][j] = dp[i][j - 1] + 1;
    • 操作三:替换元素, word1 替换 word1[i - 1] ,使其与 word2[j - 1] 相同,此时进行更改操作。只需要一次替换的操作,就可以让 word1[i - 1] 和 word2[j - 1] 相同。所以  dp[i][j] = dp[i - 1][j - 1] + 1
  • 综上,当  if (word1[i - 1] != word2[j - 1])  时取最小的,即: dp[i][j] = min({dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]}) + 1
  • 因此递推公式为
  •     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 - 1][j], dp[i][j - 1]}) + 1
        }
    
  • 接下来初始化,dp[i][0] 和 dp[0][j] 表示什么呢?
  • dp[i][0] :以下标i-1为结尾的字符串word1,和空字符串word2,最近编辑距离为dp[i][0]。
  • 那么dp[i][0]就应该是i,对word1里的元素全部做删除操作,即:dp[i][0] = i。同理dp[0][j] = j。
  • 解题代码

  •     func minDistance(word1 string, word2 string) int {
        	m, n := len(word1), len(word2)
        	dp := make([][]int, m+1)
        	for i := range dp {
        		dp[i] = make([]int, n+1)
        	}
        	for i := 0; i < m+1; i++ {
        		dp[i][0] = i // word1[i] 变成 word2[0], 删掉 word1[i], 需要 i 部操作
        	}
        	for j := 0; j < n+1; j++ {
        		dp[0][j] = j // word1[0] 变成 word2[j], 插入 word1[j],需要 j 部操作
        	}
        	for i := 1; i < m+1; i++ {
        		for j := 1; j < n+1; j++ {
        			if word1[i-1] == word2[j-1] {
        				dp[i][j] = dp[i-1][j-1]
        			} else { // Min(插入,删除,替换)
        				dp[i][j] = Min(dp[i][j-1], dp[i-1][j], dp[i-1][j-1]) + 1
        			}
        		}
        	}
        	return dp[m][n]
        }
        func Min(args ...int) int {
        	min := args[0]
        	for _, item := range args {
        		if item < min {
        			min = item
        		}
        	}
        	return min
        }