代码随想录算法训练营第五十六天|583.两个字符串的删除操作、72.编辑距离

day56 2023/03/28

一、两个字符串的删除操作

给定两个单词 word1 和 word2,找到使得 word1 和 word2 相同所需的最小步数,每步可以删除任意一个字符串中的一个字符。

示例:

  • 输入: "sea", "eat"
  • 输出: 2
  • 解释: 第一步将"sea"变为"ea",第二步将"eat"变为"ea"

分析如下:

这次是两个字符串可以相互删了,这种题目也知道用动态规划的思路来解,动规五部曲,分析如下:

1.确定dp数组(dp table)以及下标的含义

dp[i][j]:以i-1为结尾的字符串word1,和以j-1位结尾的字符串word2,想要达到相等,所需要删除元素的最少次数。

这里dp数组的定义有点点绕,大家要撸清思路。

2.确定递推公式

  • 当word1[i - 1] 与 word2[j - 1]相同的时候
  • 当word1[i - 1] 与 word2[j - 1]不相同的时候

当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

那最后当然是取最小值,所以当word1[i - 1] 与 word2[j - 1]不相同的时候,递推公式:dp[i][j] = min({dp[i - 1][j - 1] + 2, dp[i - 1][j] + 1, dp[i][j - 1] + 1});

因为 dp[i][j - 1] + 1 = dp[i - 1][j - 1] + 2,所以递推公式可简化为:dp[i][j] = min(dp[i - 1][j] + 1, dp[i][j - 1] + 1);

这里可能不少录友有点迷糊,从字面上理解 就是 当 同时删word1[i - 1]和word2[j - 1],dp[i][j-1] 本来就不考虑 word2[j - 1]了,那么我在删 word1[i - 1],是不是就达到两个元素都删除的效果,即 dp[i][j-1] + 1。

3.dp数组如何初始化

从递推公式中,可以看出来,dp[i][0] 和 dp[0][j]是一定要初始化的。

dp[i][0]:word2为空字符串,以i-1为结尾的字符串word1要删除多少个元素,才能和word2相同呢,很明显dp[i][0] = i。

4.确定遍历顺序

从递推公式 dp[i][j] = min(dp[i - 1][j - 1] + 2, min(dp[i - 1][j], dp[i][j - 1]) + 1); 和dp[i][j] = dp[i - 1][j - 1]可以看出dp[i][j]都是根据左上方、正上方、正左方推出来的。

所以遍历的时候一定是从上到下,从左到右,这样保证dp[i][j]可以根据之前计算出来的数值进行计算。

5.举例推导dp数组

以word1:"sea",word2:"eat"为例,推导dp数组状态图如下:

代码如下:

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

二、编辑距离

给你两个单词 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')

分析如下:

1.确定dp数组(dp table)以及下标的含义

dp[i][j] 表示以下标i-1为结尾的字符串word1,和以下标j-1为结尾的字符串word2,最近编辑距离为dp[i][j]

2. 确定递推公式

if (word1[i - 1] == word2[j - 1]) 那么说明不用任何编辑,dp[i][j] 就应该是 dp[i - 1][j - 1],即dp[i][j] = dp[i - 1][j - 1];

此时可能有同学有点不明白,为啥要即dp[i][j] = dp[i - 1][j - 1]呢?

那么就在回顾上面讲过的dp[i][j]的定义,word1[i - 1] 与 word2[j - 1]相等了,那么就不用编辑了,以下标i-2为结尾的字符串word1和以下标j-2为结尾的字符串word2的最近编辑距离dp[i - 1][j - 1]就是 dp[i][j]了。

在下面的讲解中,如果哪里看不懂,就回想一下dp[i][j]的定义,就明白了。

在整个动规的过程中,最为关键就是正确理解dp[i][j]的定义!

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的最近编辑距离 再加上一个操作。

操作三:替换元素,word1替换word1[i - 1],使其与word2[j - 1]相同,此时不用增删加元素。

可以回顾一下,if (word1[i - 1] == word2[j - 1])的时候我们的操作 是 dp[i][j] = dp[i - 1][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;

3. dp数组如何初始化

再回顾一下dp[i][j]的定义:

dp[i][j] 表示以下标i-1为结尾的字符串word1,和以下标j-1为结尾的字符串word2,最近编辑距离为dp[i][j]

那么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;

4. 确定遍历顺序

从如下四个递推公式:

  • dp[i][j] = dp[i - 1][j - 1]
  • dp[i][j] = dp[i - 1][j - 1] + 1
  • dp[i][j] = dp[i][j - 1] + 1
  • dp[i][j] = dp[i - 1][j] + 1

可以看出dp[i][j]是依赖左方,上方和左上方元素的,如图:

所以在dp矩阵中一定是从左到右从上到下去遍历。

 5.举例推导dp数组

代码如下:

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

复习一下之前的dp(温故而知新嘛)

组合总和Ⅳ:

注意:求组合数就是外层for遍历物品,内层for遍历背包

求排列数就是外层for遍历背包,内层for遍历物品

代码如下:

#include <bits/stdc++.h>

using namespace std;

int target;
vector<int> nums;

int main()
{
    scanf("%d",&target);
    int x;
	while(scanf("%d",&x)!=EOF) nums.push_back(x);
	
	vector<int> dp(target+1,0);//初始化dp数组
	dp[0]=1;
	for(int i=0;i<=target;i++)
	{
	    for(int j=0;j<nums.size();j++)
		   if(i>=nums[j])
		    dp[i]+=dp[i-nums[j]];
	}		
	printf("%d\n",dp[target]);
	
	return 0;
}

 爬楼梯相当于完全背包问题呢(将m赋值为2即可得到爬楼梯题目可以AC的题解)

//爬楼梯问题进阶
#include <bits/stdc++.h>

using namespace std;
int n,m;

int main()
{
	scanf("%d%d",&n,&m);
	//n表示要爬的楼梯数(相当于背包) 
	//m表示每次最多可以爬的楼梯数(相当于物品)
	vector<int> dp(n+1,0); 
	dp[0]=1;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		if(i-j>=0)
		  dp[i]+=dp[i-j];
	}
	printf("%d\n",dp[n]);
	
	return 0;
} 

 零钱兑换

#include <bits/stdc++.h>

using namespace std;
int amount;
vector<int> coins;

int main()
{
    scanf("%d",&amount);
	int x;
	while(scanf("%d",&x)!=EOF) coins.push_back(x);
	
	vector<int> dp(amount+1,INT_MAX); 
	dp[0]=0;//初始化dp数组 
	for(int i=0;i<coins.size();i++)//先遍历物品
	{
	    for(int j=coins[i];j<=amount;j++)
	    if(dp[j-coins[i]]!=INT_MAX)
		  dp[j]=min(dp[j],dp[j-coins[i]]+1);	
	} 
	if(dp[amount]!=INT_MAX)
	printf("%d\n",dp[amount]);
	else printf("-1\n");
	
	return 0;
}

完全平方数代码如下:(完全背包) 

#include <bits/stdc++.h>

using namespace std;
int n;

int main()
{
	scanf("%d",&n);
	int x;
	//dp[i]表示和为i的完全平方数的最少数量 
	//n代表背包
	//每个完全平方数代表物品 
	vector<int> dp(n+1,INT_MAX);
	dp[0]=0;
	
	for(int i=1;i*i<=n;i++)//遍历物品 
	{
		for(int j=1;j<=n;j++)//遍历背包 
		{
			if(j>=i*i)
			dp[j]=min(dp[j],dp[j-i*i]+1);
		}
	}
	
	if(dp[n]!=INT_MAX)
	printf("%d\n",dp[n]);
	else printf("-1\n");
	
	return 0;
}

 单词拆分(完全背包)

#include <bits/stdc++.h>

using namespace std;

string s;
set<string> wordSet;

int main()
{
   ios_base::sync_with_stdio(0);
   cin.tie(0),cout.tie(0);
    
   cin>>s;
   string str;
   while(cin>>str) wordSet.insert(str);
   
   //字符串s代表背包,单词代表物品
   vector<bool>dp(s.size()+1,false);
   dp[0]=true;
   //初始化dp数组
   for(int i=1;i<=s.size();i++)//遍历背包
   {
      for(int j=0;j<i;j++)//遍历物品 
      {
      	 string word=s.substr(j,i-j);
      	 if(wordSet.find(word)!=wordSet.end()&&dp[j])
      	   dp[i]=true;
	  }
   }	
   if(dp[s.size()]) cout<<"yes"<<endl;
   else cout<<"no"<<endl;
   
   return 0;	
} 

打家劫舍系列问题

Ⅰ代码如下:

//打家劫舍
#include <bits/stdc++.h>

using namespace std;
vector<int> nums;

int main()
{
	int x;
	while(scanf("%d",&x)!=EOF) nums.push_back(x);
	
	if(nums.size()==0) printf("0\n");
	if(nums.size()==1) printf("%d\n",nums[0]);
	vector<int>dp(nums.size()+1,0);
	dp[0]=nums[0];
	dp[1]=max(nums[0],nums[1]);
	
	for(int i=2;i<=nums.size();i++)
	  dp[i]=max(dp[i-2]+nums[i],dp[i-1]);
	
	printf("%d\n",dp[nums.size()]);
	
	return 0;
} 

 

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

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

相关文章

企业数字化中,BI 有何价值

在近些年的时间里&#xff0c;相关的数字化技术、理念、应用扩散到各行各业&#xff0c;让整个社会开始进行深层次的改变&#xff0c;也让城市建设这种传统的基础建设开始融合数字化&#xff0c;并利用数据结合数据中心、城市大脑等构建新型基础建设模式。 数据在这些年的时间…

AD83584D数字音频放大器

AD83584D是一款数字音频放大器&#xff0c;能够将25W&#xff08;BTL&#xff09;的功率分别驱动到一对8Ω负载扬声器&#xff0c;并将50W&#xff08;PBTL&#xff09;的功率驱动到一个4Ω负载扬声器。在24V电源下工作&#xff0c;无需外部散热器或风扇即可播放音乐。AD83584D…

第16章_多版本并发控制

第16章_多版本并发控制 &#x1f3e0;个人主页&#xff1a;shark-Gao &#x1f9d1;个人简介&#xff1a;大家好&#xff0c;我是shark-Gao&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f389;目前状况&#xff1a;23届毕业生&#xff0c;目…

数据库I (SELECT语句)

目录 一、写在前面 1.0 内容概览 1.1 SQL 分类 1.2 SQL语言的规则与规范 1.2.1 基本规则 1.2.2 SQL大小写规范 &#xff08;建议遵守&#xff09; 1.3 注释 1.4 命名规则&#xff08;暂时了解&#xff09; 二、基本的SELECT语句 2.0 SELECT... 2.1 SELECT ... FROM…

HTML5 Web Workers

HTML5 Web Workers web worker 是运行在后台的 JavaScript&#xff0c;不会影响页面的性能&#xff0c;更好的解释是&#xff0c;你可以使用web worker提供的一种简单的方法来为web内容在后台线程中运行脚本&#xff0c;这些线程在执行任务的过程中并不会干扰用户界面&#xff…

Java反射复习

Java反射复习1.动态代理2.创建动态代理3.反射4.获取Class对象5. 反射获取构造方法6. 获取成员变量7. 获取成员变量并获取值和修改值8.获取成员方法9. 获取成员方法并运行10. 面试题&#xff1a;11. 练习泛型擦除12 练习&#xff1a;修改字符串的内容13 练习&#xff0c;反射和配…

【新2023Q2模拟题JAVA】华为OD机试 - 寻找密码

最近更新的博客 华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为od机试,独家整理 已参加机试人员的实战技巧本篇题解:寻找密码 题目 小王在进行游…

Nginx 负载均衡及其高可用

优质博文&#xff1a;IT-BLOG-CN 负载均衡&#xff08;Load Balance&#xff09;&#xff1a; 意思就是分摊到多个操作单元上进行执行&#xff0c;例如Web服务器、FTP服务器、企业关键应用服务器和其他任务服务器等&#xff0c;从而共同完成工作任务。负载均衡建立在现有的网络…

面试官问 : SimpleDateFormat 不是线程安全的,你有了解过吗?

前言 金三银四又有战况&#xff1a; 我们的看官&#xff0c;不能白白牺牲&#xff01; 现在&#xff0c;立刻&#xff0c;马上&#xff0c;跟我开始复现 &#xff01; 开始看我源码分析&#xff01; 开始了解怎么解决&#xff01; 正文 复现代码 多线程操作使用SimpleDateForma…

ChatGPT的平替来了?一文总结 ChatGPT 的开源平替,你值得拥有

文章目录【AIGC精选】总结 ChatGPT 的开源平替&#xff0c;你值得拥有1.斯坦福发布 Alpaca 7B&#xff0c;性能匹敌 GPT-3.52.弥补斯坦福 Alpaca 中文短板&#xff0c;中文大模型 BELLE 开源3.国产AI大模型 ChatGLM-6B 开启内测4.中文 Alpaca 模型 Luotuo 开源5. ChatGPT 最强竞…

计算机网络第一章(概述)【湖科大教书匠】

1. 各种网络 网络(Network)由若干**结点(Node)和连接这些结点的链路(Link)**组成多个网络还可以通过路由器互连起来&#xff0c;这样就构成了一个覆盖范围更大的网络&#xff0c;即互联网(互连网)。因此&#xff0c;互联网是"网络的网络(Network of Networks)"**因特…

Mybatis的二级缓存

缓存的概述和分类 概述 缓存就是一块内存空间.保存临时数据 为什么使用缓存 将数据源(数据库或者文件)中的数据读取出来存放到缓存中&#xff0c;再次获取的时候 ,直接从缓存中获取&#xff0c;可以减少和数据库交互的次数,这样可以提升程序的性能! 缓存的适用情况 …

38-二叉树练习-LeetCode145二叉树的后序遍历

题目 给你一棵二叉树的根节点 root &#xff0c;返回其节点值的 后序遍历 。 示例 1&#xff1a; 输入&#xff1a;root [1,null,2,3] 输出&#xff1a;[3,2,1] 示例 2&#xff1a; 输入&#xff1a;root [] 输出&#xff1a;[] 示例 3&#xff1a; 输入&#xff1a;ro…

让chatGPT当我的老师如何? 通过和chatGPT交互式学习,了解在ES中,一条JSON数据是如何写到磁盘上的

最近一直有一个问题&#xff0c;如鲠在喉。争取早一天解决&#xff0c;早一天踏踏实实的睡觉。 问题是&#xff1a;在ES中&#xff0c;一条JSON数据是如何写入到磁盘上的&#xff1f; 如何解决这个问题&#xff1f;我想到了chatGPT&#xff0c;还有lucene的学习资料。这篇文章&…

【机器学习】决策树(理论)

决策树&#xff08;理论&#xff09; 目录一、何为决策树1、决策树的组成2、决策树的构建二、熵1、熵的作用2、熵的定义3、熵的计算4、条件熵的引入5、条件熵的计算三、划分选择1、信息增益&#xff08; ID3 算法选用的评估标准&#xff09;2、信息增益率&#xff08; C4.5 算法…

DetectGPT:使用概率曲率的零样本机器生成文本检测

DetectGPT的目的是确定一段文本是否由特定的llm生成&#xff0c;例如GPT-3。为了对段落 x 进行分类&#xff0c;DetectGPT 首先使用通用的预训练模型&#xff08;例如 T5&#xff09;对段落 ~xi 生成较小的扰动。然后DetectGPT将原始样本x的对数概率与每个扰动样本~xi进行比较。…

Springboot 多线程分批切割处理 大数据量List集合 ,实用示例

前言 哲学提问镇贴&#xff1a; 不了解异步怎么使用的看官&#xff0c; 可阅&#xff1a; SpringBoot 最简单的使用异步线程案例 Async_小目标青年的博客-CSDN博客 Springboot Async异步扩展使用 结合 CompletableFuture_小目标青年的博客-CSDN博客 想了解更多关于批量list处…

25- 卷积神经网络(CNN)原理 (TensorFlow系列) (深度学习)

知识要点 卷积神经网络的几个主要结构: 卷积层&#xff08;Convolutions&#xff09;: Valid :不填充&#xff0c;也就是最终大小为卷积后的大小. Same&#xff1a;输出大小与原图大小一致&#xff0c;那么N ​变成了​N2P. padding-零填充. 池化层&#xff08;Subsampli…

《程序员面试金典(第6版)》面试题 08.08. 有重复字符串的排列组合(回溯算法,全排列问题)C++

题目描述 有重复字符串的排列组合。编写一种方法&#xff0c;计算某字符串的所有排列组合。 示例1: 输入&#xff1a;S “qqe” 输出&#xff1a;[“eqq”,“qeq”,“qqe”] 示例2: 输入&#xff1a;S “ab” 输出&#xff1a;[“ab”, “ba”] 提示: 字符都是英文字母。…

Mybatis持久层框架 | Lombok搭建

&#x1f497;wei_shuo的个人主页 &#x1f4ab;wei_shuo的学习社区 &#x1f310;Hello World &#xff01; Lombok Lombok项目是一个java库&#xff0c;它可以自动插入到编辑器和构建工具中&#xff0c;增强java的性能。不需要再写getter、setter或equals方法&#xff0c;只要…