作者: 负雪明烛
id: fuxuemingzhu
个人博客: http://fuxuemingzhu.cn/


题目地址:https://leetcode.com/problems/edit-distance/description/

题目描述

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')

题目大意

给了两个字符串,现在有三种操作,问最少做多少次操作,能使word1变成word2。三种操作是:

  1. 插入一个字符
  2. 删除一个字符
  3. 替换一个字符

解题方法

程序的世界真是其妙无穷。

很多人的解法直接上来就是动态规划,其实少了这个动态规划怎么想出来的过程。动态规划的思路就是 递归 => 记忆化搜索 => 动态规划,一步步提升转化出来的,大家都在讲动态规划,其实少了前两步的思考过程。

我现在详细讲解下递归 => 记忆化搜索 => 动态规划的优化过程。

递归

这个题和最长公共子序列非常相似,需要判断最后的一个字符是否相等:

  • 如果相等,则最后一个字符不用做任何操作,那么只用计算除去最后一个字符外的前面的子串的编辑距离即可。
  • 如果不等,则最后一个字符需要进行替换操作,那么只用计算除去最后一个字符外的前面的子串的编辑距离 ,再 +1(最后一个字符的替换操作),即可把word1变成word2。

图源花花酱:

代码比较简单,需要注意的是初始化的数组大小是 L1 + 1L2 + 1,因为函数的意义是 [0, L1], [0, L2] 区间变成相等的最小操作次数,闭区间。

可以按照上面的思路,进行暴力的求解。但是会超时 TLE。

C++代码如下:

class Solution {
public:
int minDistance(string word1, string word2) {
// cout << "word1: " << word1 << " word2: " << word2 << endl;
int M = word1.size();
int N = word2.size();
if (M == 0) return N;
if (N == 0) return M;
if (word1[M - 1] == word2[N - 1]) {
return minDistance(word1.substr(0, M - 1), word2.substr(0, N - 1));
}
return 1 + min(min(minDistance(word1.substr(0, M - 1), word2),
minDistance(word1, word2.substr(0, N - 1))),
minDistance(word1.substr(0, M - 1), word2.substr(0, N - 1)));
}
};

记忆化搜索

上面的超时的原因是会有重复的计算,同样的一个状态会被不同的分支走多次,因此可以使用记忆化搜索,保存一下走过的状态的结果,如果另外一个分支走到了这个状态,那么可以直接查找之前的计算结果。

Python代码如下:

class Solution(object):
def minDistance(self, word1, word2):
"""
:type word1: str
:type word2: str
:rtype: int
"""
L1, L2 = len(word1), len(word2)
dp = [[-1] * (L2 + 1) for _ in range(L1 + 1)]
return self.getDistance(word1, word2, dp, L1, L2) def getDistance(self, word1, word2, dp, pos1, pos2):
if pos1 == 0: return pos2
if pos2 == 0: return pos1
if dp[pos1][pos2] >= 0: return dp[pos1][pos2] res = 0
if word1[pos1 - 1] == word2[pos2 - 1]:
res = self.getDistance(word1, word2, dp, pos1 - 1, pos2 - 1)
else:
res = min(self.getDistance(word1, word2, dp, pos1 - 1, pos2 - 1),
self.getDistance(word1, word2, dp, pos1, pos2 - 1),
self.getDistance(word1, word2, dp, pos1 - 1, pos2)) + 1
dp[pos1][pos2] = res
return res

C++代码如下:

class Solution {
public:
int minDistance(string word1, string word2) {
const int L1 = word1.size();
const int L2 = word2.size();
dp_ = vector<vector<int>>(L1 + 1, vector<int>(L2 + 1, -1));
return getDistance(word1, word2, L1, L2);
}
private:
vector<vector<int>> dp_;
int getDistance(string& word1, string& word2, int l1, int l2) {
if (l1 == 0) return l2;
if (l2 == 0) return l1;
if (dp_[l1][l2] >= 0) return dp_[l1][l2]; int res = 0;
if (word1[l1 - 1] == word2[l2 - 1])
res = getDistance(word1, word2, l1 - 1, l2 - 1);
else
res = min(getDistance(word1, word2, l1 - 1, l2 - 1),
min(getDistance(word1, word2, l1 - 1, l2),
getDistance(word1, word2, l1, l2 - 1))) + 1;
dp_[l1][l2] = res;
return res;
}
};

动态规划

记忆化搜索是自顶向下的操作,即如果求 word1 和 word2 的编辑距离 需要求除掉最后一个字符外的字符串的 编辑距离,依次递归下去。是个把问题规模逐渐变小的过程。

动态规划是自底向上的操作,即先求出最开始的边界条件,然后一步步添加字符,直到添加成 word1 和 word2 的时候,最后的编辑距离。是个把问题规模逐渐变大的过程。

知道了记忆化搜索之后,很容易改成动态规划。这两者的边界是一样的,只不过从递归转成了循环。

python代码如下:

class Solution(object):
def minDistance(self, word1, word2):
"""
:type word1: str
:type word2: str
:rtype: int
"""
L1, L2 = len(word1), len(word2)
dp = [[0] * (L2 + 1) for _ in range(L1 + 1)]
for i in range(L1 + 1):
dp[i][0] = i
for j in range(L2 + 1):
dp[0][j] = j
for i in range(1, L1 + 1):
for j in range(1, L2 + 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
return dp[L1][L2]

C++代码如下:

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

日期

2018 年 12 月 10 日 —— 又是周一!
2020 年 4 月 6 日 —— 又是周一!

【LeetCode】72. Edit Distance 编辑距离(Python & C++)的更多相关文章

  1. [LeetCode] 72. Edit Distance 编辑距离

    Given two words word1 and word2, find the minimum number of operations required to convert word1 to  ...

  2. leetCode 72.Edit Distance (编辑距离) 解题思路和方法

    Edit Distance Given two words word1 and word2, find the minimum number of steps required to convert  ...

  3. [LeetCode] 72. Edit Distance(最短编辑距离)

    传送门 Description Given two words word1 and word2, find the minimum number of steps required to conver ...

  4. LeetCode - 72. Edit Distance

    最小编辑距离,动态规划经典题. Given two words word1 and word2, find the minimum number of steps required to conver ...

  5. [leetcode]72. Edit Distance 最少编辑步数

    Given two words word1 and word2, find the minimum number of operations required to convert word1 to ...

  6. 72. Edit Distance(编辑距离 动态规划)

    Given two words word1 and word2, find the minimum number of operations required to convert word1 to  ...

  7. 第十八周 Leetcode 72. Edit Distance(HARD) O(N^2)DP

    Leetcode72 看起来比较棘手的一道题(列DP方程还是要大胆猜想..) DP方程该怎么列呢? dp[i][j]表示字符串a[0....i-1]转化为b[0....j-1]的最少距离 转移方程分三 ...

  8. [leetcode] 72. Edit Distance (hard)

    原题 dp 利用二维数组dp[i][j]存储状态: 从字符串A的0~i位子字符串 到 字符串B的0~j位子字符串,最少需要几步.(每一次删增改都算1步) 所以可得边界状态dp[i][0]=i,dp[0 ...

  9. 【Leetcode】72 Edit Distance

    72. Edit Distance Given two words word1 and word2, find the minimum number of steps required to conv ...

随机推荐

  1. Docker实用命令介绍

    Docker实用命令介绍 1. docker启动.关闭.停止 ╭─wil-xz in ~ 12:15:44 ╰─٩(ŏ﹏ŏ.)۶ service docker restart Redirecting ...

  2. PHP-FPM运行状态的实时查看及监控详解

    https://www.jb51.net/article/97640.htm https://blog.csdn.net/Dr_cokiy/article/details/105580758

  3. Linux之vi和vim编辑器

    目录 1. vi和vim简介 2. vi 和 vim 的三种常见模式 2.1 正常模式 2.2 插入模式 2.3 命令行模式 3. 三种模式间的切换 4. 常用快捷键案例 5. 常用命令 1. vi和 ...

  4. 9. Delete Node in a Linked List

    Write a function to delete a node (except the tail) in a singly linked list, given only access to th ...

  5. Linux搭建yum仓库

    1.安装nginx 2.为nginx搭建共享目录 3.安装createrepo,创建存储库 4.客户端测试 1.安装nginx yum list |grep nginx #查看是否有可用的nginx包 ...

  6. SM 国密算法踩坑指南

    各位,好久不见~ 最近接手网联的国密改造项目,由于对国密算法比较陌生,前期碰到了一系列国密算法加解密的问题. 所以这次总结一下,分享这个过程遇到的问题,希望帮到大家. 国密 什么是国密算法? 国密就是 ...

  7. A Child's History of England.50

    'Knave [man without honor]!' said King Richard. 'What have I done to thee [you] that thou [you] shou ...

  8. 如何将List集合中相同属性的对象合并

    在实际的业务处理中,我们经常会碰到需要合并同一个集合内相同属性对象的情况,比如,同一个用户短时间内下的订单,我们需要将各个订单的金额合并成一个总金额.那么用lambda表达式和HashMap怎么分别处 ...

  9. Java Maven项目搭建

    创建空项目 New Project --> Empty Project --> ... 配置JDK Project Settings --> Project 选择JDK Module ...

  10. navicat突然连接不上远程linux服务器上的mysql

    我linux服务器上的mysql是docker安装的,突然有一天我的navicat连接不上服务器上的mysql,于是开始了下面一系列的修复 1.首先登录服务器上mysql,看是否能正常登录,我发现不能 ...