作者: 负雪明烛
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. R语言与医学统计图形-【33】生存曲线、森林图、曼哈顿图

    1.生存曲线 基础包survival+扩展包survminer. survival包内置肺癌数据集lung. library(survival) library(survminer) str(lung ...

  2. linux常用目录和文件解析

    1. 一级目录 /dev 设备目录 /etc 系统配置及服务配置文件.启动命令的目录 /proc 显示内核及进程信息的虚拟文件系统 /tmp 临时文件目录 /home 普通用户家目录 /root 超级 ...

  3. Linux 软件安装位置选择指南

    Linux 软件安装   Linux 下安装软件不像 Windows 下安装这么简单,Windows 下会自动选择合适安装路径,而 Linux 下安装路径大部分完全由自己决定,我可以将软件安装到任意可 ...

  4. Python中的随机采样和概率分布(一)

    Python(包括其包Numpy)中包含了了许多概率算法,包括基础的随机采样以及许多经典的概率分布生成.我们这个系列介绍几个在机器学习中常用的概率函数.先来看最基础的功能--随机采样. 1. rand ...

  5. 微信小程序的wx.login用async和data解决code不一致的问题

    由于wx.login是异步函数,导致在我们获取微信小程序返回的code去请求我们的登录接口时code的值会异常.现在用promise封装一下,将他success的结果返回,在登陆函数中await就可以 ...

  6. 看动画学算法之:二叉搜索树BST

    目录 简介 BST的基本性质 BST的构建 BST的搜索 BST的插入 BST的删除 简介 树是类似于链表的数据结构,和链表的线性结构不同的是,树是具有层次结构的非线性的数据结构. 树是由很多个节点组 ...

  7. 16. Linux find查找文件及文件夹命令

    find的主要用来查找文件,查找文件的用法我们比较熟悉,也可用它来查找文件夹,用法跟查找文件类似,只要在最后面指明查找的文件类型 -type d,如果不指定type类型,会将包含查找内容的文件和文件夹 ...

  8. java9 模块化 jigsaw

    java9并没有在语言层面做出很多改变,而是致力于一些新特性,如模块化,其核心就是解决历史遗留问题,为以后的jar包森林理清道路.模块化是一个很大的命题,就不讲那么细致了,关于java9的特性也有很多 ...

  9. 【php安全】 register_argc_argv 造成的漏洞分析

    对register_argc_argv的分析 简介 使用 cli模式下,不论是否开始register_argc_argv,都可以获取命令行或者说外部参数 web模式下,只有开启了register_ar ...

  10. Android Https相关完全解析

    转载: 转载请标明出处: http://blog.csdn.net/lmj623565791/article/details/48129405: 本文出自:[张鸿洋的博客] 一.概述 其实这篇文章理论 ...