【leetcode热题100】编辑距离

给你两个单词 word1 和 word2, 请返回将 word1 转换成 word2 所使用的最少操作数  。

你可以对一个单词进行如下三种操作:

  • 插入一个字符
  • 删除一个字符
  • 替换一个字符

示例 1:

输入:word1 = "horse", word2 = "ros"
输出:3
解释:
horse -> rorse (将 'h' 替换为 'r')
rorse -> rose (删除 'r')
rose -> ros (删除 'e')

示例 2:

输入:word1 = "intention", word2 = "execution"
输出:5
解释:
intention -> inention (删除 't')
inention -> enention (将 'i' 替换为 'e')
enention -> exention (将 'n' 替换为 'x')
exention -> exection (将 'n' 替换为 'c')
exection -> execution (插入 'u')

解法一 递归

我们可以发现删除一个字符和插入一个字符是等效的,对于变换次数并没有影响。例如 "a" 和 "ab" ,既可以 "a" 加上一个字符 "b" 变成 "ab",也可以是 "ab" 去掉一个字符 "b" 变成 "a"。所以下边的算法可以只考虑删除和替换。

首先,以递归的思想去考虑问题,思考如何将大问题化解为小问题。例如 horse 变为 ros,其实我们有三种可选方案。

第一种,先把 horse 变为 ro ,求出它的最短编辑距离,假如是 x,然后 hosre 变成 ros 的编辑距离就可以是 x + 1。因为 horse 已经变成了 ro,然后我们可以把 ros 的 s 去掉,两个字符串就一样了,也就是再进行一次删除操作,所以加 1。

第二种,先把 hors 变为 ros,求出它的最短编辑距离,假如是 y,然后 hosre 变成 ros 的编辑距离就可以是 y + 1。因为 hors 变成了 ros,然后我们可以把 horse 的 e 去掉,两个字符串就一样了,也就是再进行一次删除操作,所以加 1。

第三种,先把 hors 变为 ro,求出它的最短编辑距离,假如是 z,然后我们再把 e 换成 s,两个字符串就一样了,hosre 变成 ros 的编辑距离就可以是 z + 1。当然,如果是其它的例子,最后一个字符是一样的,比如是 hosrs 和 ros ,此时我们直接取 z 作为编辑距离就可以了。

最后,我们从上边所有可选的编辑距离中,选一个最小的就可以了。

上边的第一种情况,假设了 horse 变为 ro 的最短编辑距离是 x,但其实我们并不知道 x 是多少,这个怎么求呢?类似的思路,也分为三种情况,然后选最小的就可以了!当然,上边的第二种,第三种情况也是类似的。然后一直递归下去。

最后,字符串长度不断地减少,直到出现了空串,这也是我们的递归出口了,如果是一个空串,一个不是空串,假如它的长度是 l,那么这两个字符串的最小编辑距离就是 l。如果是两个空串,那么最小编辑距离当然就是 0 了。

上边的分析,很容易就写出递归的写法了。

public int minDistance(String word1, String word2) {
    if (word1.length() == 0 && word2.length() == 0) {
        return 0;
    }
    if (word1.length() == 0) {
        return word2.length();
    }
    if (word2.length() == 0) {
        return word1.length();
    }
    int x = minDistance(word1, word2.substring(0, word2.length() - 1)) + 1;
    int y = minDistance(word1.substring(0, word1.length() - 1), word2) + 1;
    int z = minDistance(word1.substring(0, word1.length() - 1), word2.substring(0, word2.length() - 1));
    if(word1.charAt(word1.length()-1)!=word2.charAt(word2.length()-1)){
        z++;
    }
    return Math.min(Math.min(x, y), z);
}

解法二 动态规划

上边的算法缺点很明显,先进行了压栈,浪费了很多时间,其次很多字符串的最小编辑距离都进行了重复计算。对于这种,很容易想到动态规划的思想去优化。

假设两个字符串是 word1 和 word2。

ans[i][j] 来表示字符串 word1[ 0, i ) (word1 的第 0 到 第 i - 1个字符)和 word2[ 0, j - 1) 的最小编辑距离。然后状态转移方程就出来了。

if ( word1[m] == word2[n] )

​ ans[m][n] = Math.min ( ans[m][n-1] + 1, ans[m-1][n] + 1, ans[m-1][n-1])

if ( word1[m] != word2[n] )

​ ans[m][n] = Math.min ( ans[m][n-1] + 1, ans[m-1][n] + 1, ans[m-1][n-1] + 1)

然后两层 for 循环,直接一层一层的更新数组就够了。

public int minDistance(String word1, String word2) {
    if (word1.length() == 0 && word2.length() == 0) {
        return 0;
    }
    if (word1.length() == 0) {
        return word2.length();
    }
    if (word2.length() == 0) {
        return word1.length();
    }
    int[][] ans = new int[word1.length() + 1][word2.length() + 1];

    //把有空串的情况更新了
    for (int i = 0; i <= word1.length(); i++) {
        ans[i][0] = i;
    }
    for (int i = 0; i <= word2.length(); i++) {
        ans[0][i] = i;
    }
    int n1 = word1.length();
    int n2 = word2.length();
    //从 1 开始遍历,从 0 开始的话,按照下边的算法取了 i - 1 会越界
    for (int i = 1; i <= n1; i++) {
        for (int j = 1; j <= n2; j++) {
            int min_delete = Math.min(ans[i - 1][j], ans[i][j - 1]) + 1;
            int replace = ans[i - 1][j - 1];
            if (word1.charAt(i - 1) != word2.charAt(j - 1)) {
                replace++;
            }
            ans[i][j] = Math.min(min_delete, replace);
        }
    }
    return ans[n1][n2];
}

时间复杂度:O(mn)。

空间复杂度:O(mn)。

如果你是顺序刷题的话,做到这里,一定会想到空间复杂度的优化,例如5题,10题,53题等等。主要想法是,看上边的算法,我们再求 ans[i][*] 的时候,我们只用到 ans[i - 1][*] 的情况,所以我们完全只用两个数组就够了。

public int minDistance(String word1, String word2) {
    if (word1.length() == 0 && word2.length() == 0) {
        return 0;
    }
    if (word1.length() == 0) {
        return word2.length();
    }
    if (word2.length() == 0) {
        return word1.length();
    }
    int[][] ans = new int[2][word2.length() + 1];

    for (int i = 0; i <= word2.length(); i++) {
        ans[0][i] = i;
    }
    int n1 = word1.length();
    int n2 = word2.length();
    for (int i = 1; i <= n1; i++) {
        //由于只用了两个数组,所以不能向以前一样一次性初始化空串,在这里提前更新 j = 0 的情况
        ans[i % 2][0] = ans[(i - 1) % 2][0] + 1;
        for (int j = 1; j <= n2; j++) {
            int min_delete = Math.min(ans[(i - 1) % 2][j], ans[i % 2][j - 1]) + 1;
            int replace = ans[(i - 1) % 2][j - 1];
            if (word1.charAt(i - 1) != word2.charAt(j - 1)) {
                replace++;
            }
            ans[i % 2][j] = Math.min(min_delete, replace);
        }
    }
    return ans[n1 % 2][n2];
}

时间复杂度:O(mn)。

空间复杂度:O(n)。

再直接点,其实连两个数组我们都不需要,只需要一个数组。改写这个可能有些不好理解,可以结合一下图示。

在更新二维数组的时候,我们都是一列一列的更新。在更新 ? 位置的时候,我们需要橙色位置的信息,也就是当前列的上一个位置,和上一列的当前位置,和上一列的上一个位置。如果我们用一个数组,当前列的上一个位置已经把上一列的上一个位置的数据覆盖掉了,所以我们要用一个变量提前保存上一列的上一个位置以便使用。

public int minDistance(String word1, String word2) {
    if (word1.length() == 0 && word2.length() == 0) {
        return 0;
    }
    if (word1.length() == 0) {
        return word2.length();
    }
    if (word2.length() == 0) {
        return word1.length();
    }
    int[] ans = new int[word2.length() + 1];

    for (int i = 0; i <= word2.length(); i++) {
        ans[i] = i;
    }
    int n1 = word1.length();
    int n2 = word2.length();
    for (int i = 1; i <= n1; i++) {
        int temp = ans[0];
        ans[0] = ans[0] + 1;
        for (int j = 1; j <= n2; j++) {
            int min_delete = Math.min(ans[j], ans[j - 1]) + 1;
            //上一列的上一个位置,直接用 temp
            int replace = temp;
            if (word1.charAt(i - 1) != word2.charAt(j - 1)) {
                replace++;
            }
            //保存当前列的信息
            temp = ans[j];
            //再进行更新
            ans[j] = Math.min(min_delete, replace);
        }
    }
    return ans[n2];
}

时间复杂度:O(mn)。

空间复杂度:O(n)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/364504.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

政安晨的机器学习笔记——演绎一个TensorFlow官方的Keras示例(对服装图像进行分类,很全面)

导语 Keras是一个高级API接口&#xff0c;用于构建和训练神经网络模型。它是TensorFlow的一部分&#xff0c;提供了一种简洁、直观的方式来创建深度学习模型。 Keras的主要特点如下&#xff1a; 简洁易用&#xff1a;Keras提供了一组简单的函数和类&#xff0c;使模型的创建和…

10、数据结构与算法——堆

一、什么是堆 堆是一种特殊的树形数据结构&#xff0c;通常实现为完全二叉树或满二叉树。堆又分为两种类型最大堆&#xff08;Max Heap&#xff09; 和 最小堆&#xff08;Min Heap&#xff09; 1.1、什么是二叉树 二叉树是一种数据结构&#xff0c;它是由n&#xff08;n≥0&a…

【数据分享】1929-2023年全球站点的逐日最低气温数据(Shp\Excel\免费获取)

气象数据是在各项研究中都经常使用的数据&#xff0c;气象指标包括气温、风速、降水、湿度等指标&#xff0c;其中又以气温指标最为常用&#xff01;说到气温数据&#xff0c;最详细的气温数据是具体到气象监测站点的气温数据&#xff01; 之前我们分享过1929-2023年全球气象站…

面试中问到的算法题。————目录树生成

前言 我在面试中遇到了算法题&#xff0c;也是我第一次面试&#xff0c;也不知道是太紧张了还是太久没刷算法题了&#xff0c;感觉压有点懵的状态&#xff0c;所以当时面试的时候没有做出来或者说只做了一半没有做完。 面试完成后&#xff0c;我又重新审视了一下题目&#xff…

【MBtiles数据格式说明】GeoServer改造Springboot番外系列一

一、MBTiles数据格式 MBTiles格式是指由MapBox制定的一种将瓦片地图数据存储到SQLite数据库中并可快速使用、管理和分享的规范&#xff0c;是一种用于即时使用和高效传输的规范。MBTiles既可以用作栅格输入数据存储&#xff0c;也可以用作WMSGetMap输出格式。规范有1.0&#xf…

linux使用iptables禁用ip

iptables是什么&#xff1f; iptables 是一个强大的开源软件&#xff0c;它是 Linux 系统内核中 netfilter 包过滤框架的一部分&#xff0c;用来实现防火墙功能。iptables 提供了一种灵活的方式来控制和管理进出以及通过 Linux 计算机的网络流量。 前提 我在云服务器上用doc…

物联网可视化平台:赋能企业数字化转型

在数字化转型的大潮中&#xff0c;企业面临着如何更好地理解和利用海量数据的挑战。物联网技术的快速发展&#xff0c;为企业提供了一个全新的视角和解决方案。通过物联网可视化平台&#xff0c;企业能够实时监控、分析和展示物联网数据&#xff0c;从而加速数字化转型的进程。…

前端构建变更:从 webpack 换 vite

现状 这里以一个 op &#xff08;内部运营管理用&#xff09;项目为例&#xff0c;从 webpack 构建改为 vite 构建&#xff0c;提高本地开发效率&#xff0c;顺便也加深对 webpack 、 vite 的了解。 vite 是前端构建工具&#xff0c;使用 一系列预配置进行rollup 打包&#x…

【目标检测】对DETR的简单理解

【目标检测】对DETR的简单理解 文章目录 【目标检测】对DETR的简单理解1. Abs2. Intro3. Method3.1 模型结构3.2 Loss 4. Exp5. Discussion5.1 二分匹配5.2 注意力机制5.3 方法存在的问题 6. Conclusion参考 1. Abs 两句话概括&#xff1a; 第一个真正意义上的端到端检测器最…

phpMyAdmin 未授权Getshell

前言 做渗透测试的时候偶然发现&#xff0c;phpmyadmin少见的打法&#xff0c;以下就用靶场进行演示了。 0x01漏洞发现 环境搭建使用metasploitable2,可在网上搜索下载&#xff0c;搭建很简单这里不多说了。 发现phpmyadmin&#xff0c;如果这个时候无法登陆&#xff0c;且也…

ubuntn挂载硬盘为只读问题

做为服务器操作系统&#xff0c;linux是很多站长经常用到的&#xff0c;那么在linux系统下如果需要新增加硬盘&#xff0c;该怎么增加呢&#xff1f;下面就来详细了解一下linux系统下添加新硬盘、分区及挂载硬盘的全过程。没有服务器的朋友可以点击了解一下阿里云1折优惠云服务…

【JS】Express.js环境配置与示例

&#x1f60f;★,:.☆(&#xffe3;▽&#xffe3;)/$:.★ &#x1f60f; 这篇文章主要介绍Express.js环境配置与示例。 学其所用&#xff0c;用其所学。——梁启超 欢迎来到我的博客&#xff0c;一起学习&#xff0c;共同进步。 喜欢的朋友可以关注一下&#xff0c;下次更新不…

力扣hot100 二叉树的右视图 DFS BFS 层序遍历 递归

Problem: 199. 二叉树的右视图 文章目录 思路&#x1f496; BFS&#x1f496; DFS 思路 &#x1f469;‍&#x1f3eb; 甜姨 &#x1f496; BFS ⏰ 时间复杂度: O ( n ) O(n) O(n) &#x1f30e; 空间复杂度: O ( n ) O(n) O(n) class Solution {public List<Integer&…

虹科技术|一文详解IO-Link Wireless技术如何影响工业无线自动化

导读&#xff1a;在工业无线自动化的飞速发展进程中&#xff0c;IO-Link Wireless技术成为了一项具有颠覆性的创新。它将IO-Link协议与无线连接完美结合&#xff0c;解决了传统通信技术在工业应用中的痛点。本文将深入解析IO-Link Wireless技术的原理、应用领域、优势以及实际案…

Docker基础(持续更新中)

# 第1步&#xff0c;去DockerHub查看nginx镜像仓库及相关信息# 第2步&#xff0c;拉取Nginx镜像 docker pull nginx# 第3步&#xff0c;查看镜像 docker images # 结果如下&#xff1a; REPOSITORY TAG IMAGE ID CREATED SIZE nginx latest 60…

鸿蒙(HarmonyOS)项目方舟框架(ArkUI)之TextPicker组件

鸿蒙&#xff08;HarmonyOS&#xff09;项目方舟框架&#xff08;ArkUI&#xff09;之TextPicker组件 一、操作环境 操作系统: Windows 10 专业版、IDE:DevEco Studio 3.1、SDK:HarmonyOS 3.1 二、TextPicker组件 TextClock组件通过文本将当前系统时间显示在设备上。支持不…

【DDD】学习笔记-限界上下文与架构

作为领域驱动战略设计的重要元素&#xff0c;限界上下文对领域驱动架构有着直接的影响。在领域驱动的架构设计过程中&#xff0c;识别限界上下文与上下文映射都是一个重要的过程。限界上下文可以作为逻辑架构与物理架构的参考模型&#xff0c;而上下文映射则非常直观地体现了系…

故障诊断 | 一文解决,CNN-SVM卷积神经网络-支持向量机组合模型的故障诊断(Matlab)

效果一览 文章概述 故障诊断 | 一文解决,CNN-SVM卷积神经网络-支持向量机组合模型的故障诊断(Matlab) 模型描述 卷积神经网络(Convolutional Neural Network,CNN)和支持向量机(Support Vector Machine,SVM)是两种常用的机器学习算法,它们在不同领域和任务中都表现出…

linux中vim的操作

(码字不易&#xff0c;关注一下吧w~~w) 命令模式&#xff1a; 当我们按下esc键时&#xff0c;我们会进入命令模式&#xff1b;当使用vi打开一个文件时也是进入命令模式。 光标移动&#xff1a; 1 保存退出&#xff1a;ZZ 2 代码格式化&#xff1a;ggG 3 光标移动&#xff…

公共用例库计划--个人版(六)典型Bug页面设计与开发

1、任务概述 本次计划的核心任务是开发一个&#xff0c;个人版的公共用例库&#xff0c;旨在将各系统和各类测试场景下的通用、基础以及关键功能的测试用例进行系统性地归纳整理&#xff0c;并以提高用例的复用率为目标&#xff0c;力求最大限度地减少重复劳动&#xff0c;提升…