算法学习笔记(8.6)-编辑距离问题

news2024/9/21 14:45:06

目录

Question:

动态规划思路:

第一步:思考每轮的决策,定义状态,从而得到dp表

第二步:找出最优子结构,进而推导出状态转移方程

第三步:确定边界条件和状态转移顺序

代码实现:

图例:

空间优化:

代码如下

编辑距离,也称为Levenshtein距离,指两个字符串之间互相转化的最少修改次数,通常用于在信息检索和自然语言处理中度量两个序列的相似度。

Question

输入两个字符串s和t,返回将s转化为t所需的最少编辑步数。

你可以在一个字符串中进行三种编辑操作:插入一个字符、删除一个字符、将字符替换成任意字符

如图所示,将kitten转化为sitting需要编辑3步,包括两次替换操作与一次添加操作;将hello转化为algo需要三步,包括两次替换操作和一次删除操作。

编辑距离问题可以很自然地利用决策树模型来解释。字符串对应树节点,一轮决策(一次编辑操作)对应树上的一条边。

如图所示,在不限制操作的情况下,每个节点都可以派生出许多条边,每条边对应一种操作,这意味着从hello转化到algo有许多种可能的途径。

从决策树的角度看,本题的目标是求解节点hello和节点之间的最短距离。

动态规划思路:

第一步:思考每轮的决策,定义状态,从而得到dp表

每一轮的决策是对字符串s进行一次编辑操作。

我们希望在编辑操作的过程中,问题的规模逐渐缩小,这样才能构建子问题。设字符串s和t的长度分别为n和m,我们先考虑两字符串尾部的字符s[n-1]和t[m-1]。

  1. 若s[n-1]和t[m-1]相同,我们可以跳过它们,直接考虑s[n-2]和t[m-2]。
  2. 若s[n-1]和t[m-1]不同,我们需要对s进行一次编辑(删除、插入、替换),使得两字符串尾部的字符相同,从而可以跳过它们,考虑规模更小的问题。

也就是说,我们在字符串s中进行的每一轮决策(编辑操作),都会使得s和t中剩余的待匹配字符发生变化。因此,状态为当前在s和t中考虑的第i和第j个字符,记为[i,j]。

状态[i,j]对应的子问题:将s的前i个字符更改为t的前j个字符所需的最少编辑步数。

至此,得到一个尺寸为(i+1)*(j+1)的二维dp表。

第二步:找出最优子结构,进而推导出状态转移方程

考虑子问题dp[i,j],对应的两个字符串的尾部字符为s[i-1]和t[j-1],可根据不同编辑操作分为图例的三种情况。

  1. 在s[i-1]之后添加t[j-1],剩余子问题dp[i,j-1]
  2. 删除s[i-1],剩余子问题dp[i-1,j]。
  3. 将s[i-1]替换为t[j-1],剩余子问题dp[i-1,j-1]。

根据以上分析,可得最优子结构:dp[i,j]的最少编辑步数等于dp[i,j-1]、dp[i-1,j]、dp[i-1,j-1]三者中的最少编辑步数,再加上本次编辑步数为1.对应的状态转移方程为:

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

请注意当s[i-1]和t[j-1]相同时,无须编辑当前字符,这种情况下的状态转移方程为:

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

第三步:确定边界条件和状态转移顺序

当两个字符串都为空时,编辑步数为0,即dp[0,0] = 0。当s为空但t不为空时,最少编辑步数等于t的长度,即首行的dp[0,j] = j。当s不为空但t为空时,最少编辑步数等于s的长度,即首列dp[i,0] = i。

代码实现:
# python 代码示例

def edit_distance_dp(s, t) :
    n, m = len(s), len(t)
    
    dp = [ [0] * (m + 1) for _ in range(n + 1)]
    
    for j in range(1, m + 1) :
        dp[0][j] = j ;
    
    for i in range(1, n + 1) :
        dp[i][0] = i ;
    
    for i in range(1, n + 1) :
        for j in range(1, m + 1) :
            if s[i - 1] == t[j - 1] :
                dp[i][j] = dp[i - 1][j - 1]
            else :
                dp[i][j] = min(dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]) + 1
    return dp[n][m]
// c++ 代码示例
int editDistanceDP(string s, string t)
{
    int n = s.length(), m = t.length() ;
    vector<vector<int>> dp(n + 1, vector<int>(m + 1, 0)) ;
    for (int i = 1 ; i <= n ; i++)
    {
        dp[i][0] = i ;
    }
    for (int j = 1 ; j <= m ; j++)
    {
        dp[0][j] = j ;
    }
    for (int i = 1 ; i <= n ; i++)
    {
        for (int j = 1 ; j <= m ; j++)
        {
            if (s[i - 1] == t[j - 1])
            {
                dp[i][j] = dp[i - 1][j - 1] ;
            }
            else
            {
                dp[i][j] = min(dp[i - 1][j], dp[i][j - 1], dp[i - 1]j - 1]) + 1 ;
            }
        }
    }
    return dp[n][m] ;
}
图例:

空间优化:

由于dp[i,j]是由上方的dp[i-1,j]、左方dp[i,j-1]、左上方dp[i-1,j-1]转移而来的,而正序遍历会丢失左上方dp[i-1,j-1],倒叙遍历无法提前构建dp[i,j-1],因此两种遍历顺序都不可取。

为此,我们可以使用一个变量leftup来暂存左上方的解dp[i-1,j-1],从而只需要考虑作坊和上方的解。此时的情况与完全背包问题相同,可以使用正序遍历。

代码如下:
# python 代码示例

def edit_distance_dp_comp(s, t) :
    n, m = len(s), len(t)
    dp = [0] * (m + 1)
    for j in range(1, m + 1) :
        dp[j] = j 
    for i in range(1, n + 1) :
        leftup = dp[0] # 暂存dp[i - 1][j - 1]
        dp[0] += 1
        for j in range(1, m + 1) :
            temp = dp[j]
            if s[i - 1] == t[j - 1] :
                dp[j] = leftup
            else :
                dp[j] = min(dp[j - 1], dp[j], leftup) + 1
        leftup = temp
    return dp[m]
// c++ 代码示例
int editDistanceDPComp(string s, string t)
{
    int n = s.length(), m = t.length() ;
    vector<int> dp(m + 1, 0) ;
    for (int j = 1 ; j <= m ; j++)
    {
        dp[j] = j ;
    }
    for (int i = 1 ; i <= n ; i++)
    {
        leftup = dp[0] ;
        dp[0]++ ;
        for (int j = 1 ; j <= m ; j++)
        {
            int temp = dp[j] ;
            if (s[i - 1] == t[j - 1])
            {
                dp[j] =leftup ;
            }
            else
            {
                dp[j] = min(dp[j], dp[j - 1], leftup) + 1 ;
            }
            leftup = temp ;
        }
    }
    return dp[m] ;
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

BUUCTF逆向wp [FlareOn4]login

按老规矩先查壳&#xff0c;但本题是html文件&#xff0c;查壳会报错 在网上查了一下&#xff0c;可以用vscode查看源代码&#xff0c;我们用VS code打开。 <!DOCTYPE Html /> <html> <head> <title>FLARE On 2017</title> </head> <…

d3dcompiler_47.dll缺失怎么修复,一步步分析d3dcompiler_47.dll文件

d3dcompiler_47.dll缺失怎么修复&#xff1f;快速教大家解决出现d3dcompiler_47.dll问题的方法&#xff0c;一步步教大家快速有效的将丢失的d3dcompiler_47.dll如何修复。 一步步修复d3dcompiler_47.dll分析 1. 重新安装受影响的程序 如果是特定程序报告缺少d3dcompiler_47.d…

Vue实现滚动元素始终固定在最底部

1. 应用场景——聊天 在聊天的时候&#xff0c;展示聊天内容的元素是可以滚动的&#xff0c;通过上下滚动来查看过往消息。不过在首次打开聊天页面以及发送新消息时需要固定在滚动的最底部以及时展示最新的消息&#xff0c;这样才能获得比较好的用户体验。 效果&#xff1a; …

每日一练,java

目录 描述示例 总结 描述 题目来自牛客网 •输入一个字符串&#xff0c;请按长度为8拆分每个输入字符串并进行输出&#xff1b; •长度不是8整数倍的字符串请在后面补数字0&#xff0c;空字符串不处理。 输入描述&#xff1a; 连续输入字符串(每个字符串长度小于等于100) 输…

Wikijs 部署教程

以下是一个 Wikijs 部署的简单教程&#xff0c;涵盖了使用 Docker 和直接安装两种方式&#xff1a; 方法一&#xff1a; 使用 Docker (推荐) Docker 是一个方便快捷的方式来部署 Wikijs&#xff0c;它可以避免许多手动配置步骤。 安装 Docker: 按照 https://docs.docker.com/…

接口测试返回参数的自动化对比!

引言 在现代软件开发过程中&#xff0c;接口测试是验证系统功能正确性和稳定性的核心环节。接口返回参数的对比不仅是确保接口功能实现的手段&#xff0c;也是测试过程中常见且重要的任务。为了提高对比的效率和准确性&#xff0c;我们可以通过自动化手段实现这一过程。本文将…

android CameraX构建相机拍照

Android CameraX 是一个 Jetpack 支持库&#xff0c;旨在简化相机应用的开发工作。它提供了一致且易用的API接口&#xff0c;适用于大多数Android设备&#xff0c;并可向后兼容至Android 5.0&#xff08;API级别21&#xff09;。 CameraX解决了在多种设备上实现相机功能时所遇…

LLM代理应用实战:构建Plotly数据可视化代理

如果你尝试过像ChatGPT这样的LLM&#xff0c;就会知道它们几乎可以为任何语言或包生成代码。但是仅仅依靠LLM是有局限的。对于数据可视化的问题我们需要提供一下的内容 描述数据:模型本身并不知道数据集的细节&#xff0c;比如列名和行细节。手动提供这些信息可能很麻烦&#…

javascipt学习笔记一

JavaScript基础day1 一、编程语言 1.编程 编程&#xff1a;就是让计算机为解决某个问题而使用某种编程设计语言编写程序代码&#xff0c;最终得到结果的过程 计算机程序&#xff1a; 就是计算机所执行的一系列的指令集合。 2.计算机语言 计算机语言指的是用于人与计算机之间通…

Manim的代码练习02:在manim中Dot ,Arrow和NumberPlane对象的使用

Dot&#xff1a;指代点对象或者表示点的符号。Arrow&#xff1a;指代箭头对象&#xff0c;包括直线上的箭头或者向量箭头等。NumberPlane&#xff1a;指代数轴平面对象&#xff0c;在Manim中用来创建包含坐标轴的数学坐标系平面。Text&#xff1a;指代文本对象&#xff0c;用来…

国产数据库VastBase与C/C++程序适配

背景 2022年底的项目&#xff0c;记录一下。 某项目后台使用C程序开发&#xff0c;使用的是OCI连接Oracle数据库。现需要做去O国产化适配改造。 本文聊聊C/C应用程序如何使用VastBase替换Oracle。 编译适配 开发包获取 从VastBase官方或接口人处获取OCI开发包&#xff0c;包含…

品牌产业出海指南如何搭建国际化架构的跨境电商平台?

在“品牌&产业出海指南 – 成功搭建跨境电商平台”系列中&#xff0c;我们将从电商分销系统、跨境平台商城/多商户商城系统和国际化架构三个方面对帮助您梳理不同平台模式的优缺点、应用场景、开发重点和运营建议。 在“品牌&产业出海指南 – 成功搭建跨境电商平台”系…

verITAS:大规模验证图像转换

1. 引言 斯坦福大学Trisha Datta、Binyi Chen 和 Dan Boneh 2024年论文《VerITAS: Verifying Image Transformations at Scale》&#xff0c;开源代码实现见&#xff1a; https://github.com/zk-VerITAS/VerITAS&#xff08;RustPython&#xff09; 依赖https://github.com/a…

Java的高级特性

类的继承 继承是从已有的类中派生出新的类&#xff0c;新的类能拥有已有类的属性和行为&#xff0c;并且可以拓展新的属性和行为 public class 子类 extends 父类{子类类体 } 优点 代码的复用 提高编码效率 易于维护 使类与类产生关联&#xff0c;是多态的前提 缺点 类缺乏独…

纯vue+js实现数字0到增加到指定数字动画效果功能

关于数字增加动画效果网上基本上都是借助第三方插件实现的,就会导致有的项目安装插件总会出问题,所有最好使用原生vue+js实现,比较稳妥 纯vue+js实现数字0到增加到指定数字动画效果功能 vue+js 实现数字增加动画功能 效果图 其中,关于数字变化的间隔时间,延时效果都可…

Linux环境下激活使用Navicat

Linux环境下激活使用Navicat 解压navicat15-premium-linux.zip文件&#xff0c;存放目录自行定义&#xff0c;此处我解压到/home/admin/Downloads&#xff0c;下载地址&#xff1a;https://www.123pan.com/s/hyS7Vv-zJCR.html # 在桌面创建临时目录 mkdir ~/Desktop/navicat…

Flutter和React Native(RN)的比较

Flutter和React Native&#xff08;RN&#xff09;都是用于构建跨平台移动应用程序的流行框架。两者都具有各自的优势和劣势&#xff0c;选择哪个框架取决于您的具体需求和项目。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司&#xff0c;欢迎交流合作。 以下是…

算法学习笔记(8.5)-零钱兑换问题二

目录 Question&#xff1a; 动态规划思路&#xff1a; 代码实现&#xff1a; 空间优化代码 Question&#xff1a; 给定n种硬币&#xff0c;第i种硬币的面值为coins[i-1],目标金额为amt&#xff0c;每种硬币可以重复选取&#xff0c;问凑出目标金额的硬币组合数量。 动态规划思路…

教你如何白嫖ioDraw

ioDraw推出了这个活动&#xff0c;大家看看~ 领取页面 邀请奖励 您和好友都将获得7天的会员权益 邀请攻略 活动介绍 1. 每成功邀请一个新用户&#xff0c;邀请者与被邀请者均可获得7天会员 2. 每年最高可得350天 3. 新用户是指首次登录ioDraw的用户 获取邀请链接步骤 1. 浏…

VS2019配置OpenGL的库

OpenGL环境的配置&#xff08;GLUT安装教程&#xff09;_opengl官网-CSDN博客 我是根据这篇来配置的&#xff0c;然后&#xff1a; 引用的头文件不是<OpenGL/glut.h>而是<GL/glut.h>&#xff0c;然后就可以运行了。