Day41 | 动态规划 :完全背包应用 完全平方数单词拆分(类比爬楼梯)

Day41 | 动态规划 :完全背包应用 完全平方数&&单词拆分(类比爬楼梯)

动态规划应该如何学习?-CSDN博客

01背包模板 | 学习总结-CSDN博客

完全背包模板总结-CSDN博客

难点:

代码都不难写,如何想到完全背包并把具体问题抽象为完全背包才是关键

文章目录

  • Day41 | 动态规划 :完全背包应用 完全平方数&&单词拆分(类比爬楼梯)
    • 279.完全平方数
      • 思路分析:
      • 1.回溯法
      • 2.记忆化搜索
      • 3.动态规划
      • 4.滚动数组
    • 139.单词拆分(类比爬楼梯)
      • 思路分析:
        • 类比爬楼梯
        • 而本题中呢
        • 难理解的点:||dp[i]的作用
      • 1.回溯法
      • 2.记忆化搜索
      • 3. 1:1翻译为动态规划

279.完全平方数

279. 完全平方数 - 力扣(LeetCode)

思路分析:

dfs(i,c)的含义是从前i个数里面选,能凑够容量c的完全平方数的最少数量

直观想法:

n是背包的容量,物品就是1到n这些数字

w重量数组为{1,2,3,4…n}。由于根号n*根号n=n,我们加的时候还是加的这些数字的平方数而不是它本身,所以w其实就是{1,2,3…根号n}到根号n就行了,后面的数平方一下肯定比n大,也没必要加了

v价值数组全都为1,因为选一个数只能给数量加1

那就和322. 零钱兑换 - 力扣(LeetCode)一模一样了

只是那道题w是硬币的面值,而这道题是1-n这些数字本身的数值

笔者这么理解就可以写出来了,看了题解后,其实w数组更加清晰的说是:完全平方数才是他们本身的重量

{1,4,9,16,25…}这样的吗,上面的{1,2,3,4}这些可以看做是序号i,{1,4,9,16}是序号i对应的完全平方数i*i

把 1,4,9,16,⋯ 这些完全平方数视作物品重量,物品价值都是 1。由于每个数(物品)选的次数没有限制,所以本题是一道标准的完全背包问题

和零钱兑换一样,就是选或不选i²的问题

dp[i][c]=min(dp[i-1][c],dp[i][c-i*i]);

image-20241109155109879

1.回溯法

1.参数和返回值

c是背包容量,本题是前i个数的完全平方数i²要凑成的数

i是物品编号,本题的物品编号i的完全平方数就是我们的物品,所以有这两个参数就够了

int dfs(int i,int c)

2.终止条件

i等于0,说明已经没有数可以选了,我们dfs的含义是前i个数凑成c,所以0和0前面已经没有数字了

此时如果要凑成的数c是0,说明我们找到了合法的方案,返回0(因为求的是物品的个数而不是凑成的方案数量,方案数量的话返回的就是1)

否则的话说明当前的分支不是合法的方法,返回正无穷(int最大值除以2是为了防止返回值+1操作溢出)

		if(i==0)if(c==0)return 0;elsereturn INT_MAX/2;

3.本层逻辑

如果说要凑的数c已经比i²小了,那就是不选i,继续往下递归,看能不能放上i-1

如果比i²大,那就在选或不选中挑一个最小值进行返回

		if(c<i*i)return dfs(i-1,c);return min(dfs(i-1,c),dfs(i,c-i*i)+1);

完整代码:

我们传入的时候物品编号从根号n开始就好,因为要凑成n,而根号n*根号n=n,比根号n大的数的平方一定比n大

当然是超时的

class Solution {
public:int dfs(int i,int c){if(i==0)if(c==0)return 0;elsereturn INT_MAX/2;if(c<i*i)return dfs(i-1,c);return min(dfs(i-1,c),dfs(i,c-i*i)+1);}int numSquares(int n) {return dfs(sqrt(n),n);}
};

2.记忆化搜索

就是还是全都初始化为-1,每次返回前给dp赋值,碰到不是-1的那就是算过的,那就直接返回计算过的结果,不需要再次递归了

完整代码:

class Solution {
public:int dfs(int i,int c,vector<vector<int>>& dp){if(i==0)if(c==0)return 0;elsereturn INT_MAX;if(dp[i][c]!=-1)return dp[i][c];if(c<i*i)return dp[i][c]=dfs(i-1,c,dp);return dp[i][c]=min(dfs(i-1,c,dp),dfs(i,c-i*i,dp)+1);}int numSquares(int n) {vector<vector<int>> dp(sqrt(n)+1,vector<int>(n+1,-1));return dfs(sqrt(n),n,dp);}
};

3.动态规划

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

dp[i][j]前i个数的完全平方数i²凑成j的最少方案数量

i是物品编号

j是当前要凑的数(背包容量)

2.确定递推公式

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

3.dp数组如何初始化

在回溯中,递归终止条件是i==0&&c==0,返回0

那就是dp[0][0]就是0,其他的都是正无穷(INT_MAX/2)

4.确定遍历顺序

和完全背包一样,先遍历物品在遍历容量,从前往后遍历

完整代码:

class Solution {
public:int numSquares(int n) {vector<vector<unsigned>> dp(sqrt(n)+1,vector<unsigned>(n+1,INT_MAX/2));dp[0][0]=0;for(int i=1;i<=sqrt(n);i++)for(int j=0;j<=n;j++)if(j<i*i)dp[i][j]=dp[i-1][j];else   dp[i][j]=min(dp[i-1][j],dp[i][j-i*i]+1);return dp[sqrt(n)][n];}
};

4.滚动数组

和01背包优化一样,把第一维直接删掉就行

class Solution {
public:int numSquares(int n) {vector<unsigned> dp(n+1,INT_MAX/2);dp[0]=0;for(int i=1;i<=sqrt(n);i++)for(int j=i*i;j<=n;j++)dp[j]=min(dp[j],dp[j-i*i]+1);return dp[n];}
};

139.单词拆分(类比爬楼梯)

139. 单词拆分 - 力扣(LeetCode)

思路分析:

这道题和爬楼梯、组合总和IV基本一模一样,求的都是排列数量,只是多加了一个条件

Day40 | 动态规划 :完全背包应用 组合总和IV(类比爬楼梯)-CSDN博客

我们这么想,把wordDict里面的单词看做我们的物品,而把s这个字符串看做背包

类比爬楼梯

在70.爬楼梯中,我们每次从 nums 中选一个数,作为往上爬的台阶数,问爬 target 个台阶有多少种方案。70 那题可以看作 nums=[1,2],因为每次只能爬 1 个或 2 个台阶。

dp[i]的含义就是爬上第i个台阶的方案数量

1.在那道题中

我们的代码是

dp[i]=dp[i-1]+dp[i-2]

2.如果说我们一次可以爬k个台阶,当然k要比target(要爬的总楼梯数量)小

dp[i]=dp[i-1]+dp[i-2]+dp[i-3]+....+dp[i-k]
//等价于
for(int j=1;j<=k;j++)dp[i]+=dp[i-j];

3.如果说我们一次可以爬的台阶数量是nums数组里面的,那么j就是nums[j],上面的1-k就相当于这里的nums数组的遍历

dp[i]=dp[i-nums[1]]+dp[i-nums[2]]+dp[i-nums[3]]+....+dp[i-nums[nums.size()-1]]
//等价于
for(int j=0;j<nums.size();j++)dp[i]+=dp[i-nums[j]];

相当于,在第一种情况中

nums数组为{1,2}

在第二种情况中

nums数组为{1,2,3,…,k}

而本题中呢

我们要爬到s.size()这个台阶上,每次可以爬的台阶数量呢就是wordDict[i].size()这么多个台阶

从这里开始就和爬楼梯不同了,因为字符串s这个背包是一个有形状的背包

类似于这样

image-20241109173408456

这个背包长这个倒霉样子,如果不先放平行四边形,那这个背包永远也不会装满,即使你的三角形大小为3,平行四边形可能为9

这个意思就是说,我们这次往背包里面放物品不仅仅取决于背包容量,还要看它的形状,形状那就体现在咱们要放的字符串和背包的这一截一不一样

用爬楼梯的话说就是,能爬到s.size()级台阶的方法不能只是数量大小上面达到了,你走过的路径,你选择的字符串拼起来要能组成我的s字符串才是合法的方案,才能返回true

体现在代码中就是:

image-20241109174343757

爬楼梯这部分代码对应到本题,代码就会变成

for(int j=0;j<wordDict.size();j++)if(wordDict[j].size()<=i){string str;str=s.substr(i-wordDict[j].size(),wordDict[j].size());dp[i]=(str==wordDict[j]&&dp[i-wordDict[j].size()])||dp[i];}

dp[i]的含义是我们能不能用wordDict里面的字符串拼成s.begin()到s.begin()+i这个字符串,可以的话就是true,不行就是false,i是遍历背包容量

可以看到,在第一个if判断我们目前的背包可以装下这个字符串的时候,并不会立马放进去

(放进去就是(dp[i-wordDict[j].size()],不放就是dp[i])

而是把我们要放的字符串wordDict[i]和背包的某一截进行比较,如果一模一样的话,才会放进去

如果大家有点蒙圈的话,就再次对比一下爬楼梯,爬楼梯是看我这次爬nums[j]个台阶能不能爬到第i个台阶,这里我选这么些字符串(wordDict[j])能不能拼成s.begin()到s.begin()+i这个字符串

我可能是从第一个台阶爬两个到第三个台阶

我可能是从第二个台阶爬一个到第三个台阶

我可能是从已经拼好的部分再加上wordDict[1]就能拼成

我可能是从已经拼好的部分再加上wordDict[2]就能拼成

所以要把物品都给遍历一遍

难理解的点:||dp[i]的作用

这个是用来保留dp[i]的结果的

假如我运气好第一次循环就拼好了,dp[i]就是true,后面不管结果如果我就是true

但如果没有这个,我后面如果都没拼好过,那dp[i]就是false,就代表没有合法的方案,那这明显是错的

1.回溯法

1.参数和返回值

c是背包容量,我们要拼的字符串就是s.begin()到s.begin()+c

bool dfs(int c,string &s,vector<string>& wordDict)

2.终止条件

c==0,我们找到了合法方案,返回true

if(c==0)return true;

3.本层逻辑

这里就如上面所说,我就不过多解释了

		bool res=false;for(int i=0;i<wordDict.size();i++)if(c>=wordDict[i].size()){string str=s.substr(c-wordDict[i].size(),wordDict[i].size());res=(str==wordDict[i]&&dfs(c-wordDict[i].size(),s,wordDict))||res;}return res;

完整代码:

当然是超时的

class Solution {
public:bool dfs(int c,string &s,vector<string>& wordDict){if(c==0)return true;bool res=false;for(int i=0;i<wordDict.size();i++)if(c>=wordDict[i].size()){string str=s.substr(c-wordDict[i].size(),wordDict[i].size());res=(str==wordDict[i]&&dfs(c-wordDict[i].size(),s,wordDict))||res;}return res;}bool wordBreak(string s, vector<string>& wordDict) {return dfs(s.size(),s,wordDict);}
};
//lambda
class Solution {
public:bool wordBreak(string s, vector<string>& wordDict) {function<bool(int)> dfs=[&](int c)->bool{if(c==0)return true;bool res=false;for(int i=0;i<wordDict.size();i++)if(c>=wordDict[i].size()){string str=s.substr(c-wordDict[i].size(),wordDict[i].size());res=(str==wordDict[i]&&dfs(c-wordDict[i].size()))||res;}return res;};return dfs(s.size());}
};

2.记忆化搜索

就是还是全都初始化为-1,每次返回前给dp赋值,碰到不是-1的那就是算过的,那就直接返回计算过的结果,不需要再次递归了

class Solution {
public:bool dfs(int c,string &s,vector<string>& wordDict,vector<int>& dp){if(c==0)return true;if(dp[c]!=-1)return dp[c];bool res=false;for(int i=0;i<wordDict.size();i++)if(c>=wordDict[i].size()){string str=s.substr(c-wordDict[i].size(),wordDict[i].size());res=(str==wordDict[i]&&dfs(c-wordDict[i].size(),s,wordDict,dp))||res;}return dp[c]=res;}bool wordBreak(string s, vector<string>& wordDict) {vector<int> dp(s.size()+1,-1);return dfs(s.size(),s,wordDict,dp);}
};
class Solution {
public:bool wordBreak(string s, vector<string>& wordDict) {vector<int> dp(s.size()+1,-1);function<bool(int)> dfs=[&](int c)->bool{if(c==0)return true;if(dp[c]!=-1)return dp[c];bool res=false;for(int i=0;i<wordDict.size();i++)if(c>=wordDict[i].size()){string str=s.substr(c-wordDict[i].size(),wordDict[i].size());res=(str==wordDict[i]&&dfs(c-wordDict[i].size()))||res;}return dp[c]=res;};return dfs(s.size());}
};

3. 1:1翻译为动态规划

回溯终止条件为c==0的时候返回true,那动态规划dp数组初始化就是dp[0]=true

class Solution {
public:bool wordBreak(string s, vector<string>& wordDict) {vector<bool> dp(s.size()+1,false);dp[0]=true;for(int i=1;i<=s.size();i++)for(int j=0;j<wordDict.size();j++)if(wordDict[j].size()<=i){string str;str=s.substr(i-wordDict[j].size(),wordDict[j].size());dp[i]=(dp[i-wordDict[j].size()]&&str==wordDict[j])||dp[i];}return dp[s.size()];}
};

和完全背包一样,是求排列的先遍历容量后遍历物品的写法

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

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

相关文章

ELK-Kibana配置

文章目录 一、什么是Kibana、有什么用&#xff1f;二、Kibana的安装与基本配置1. 下载Kibana安装包2. 解压安装包3. 修改Kibana配置文件4. 添加用户目录权限5. 启动Kibana6. 访问Kibana 三、Kibana的使用Index pattern的配置查看收集到的数据画图 一、什么是Kibana、有什么用&a…

kdump 应该怎么安装 linux-crashdump kdump-tools

sudo apt install linux-crashdump sudo apt install crash sudo apt install kdump-tools 1. 两个工具的关系 linux-crashdump kdump-tools 在 Ubuntu 上安装 kdump 功能&#xff0c;这两个包都是相关的&#xff0c;但有不同的作用. linux-crashdump 是一个元包&#xff08;…

pdf转excel;pdf中表格提取

一、问题描述 在工作中或多或少会遇到&#xff1a;需要将某份pdf中的表格数据提取出来&#xff0c;以便能够“修改使用”数据 可将pdf中的表格提取出来&#xff0c;解决办法还有点复杂 尤其涉及“pdf中表格不是标准的单元格”的时候&#xff0c;提取数据到excel不太容易 比…

ElasticSearch备考 -- 集群配置常见问题

一、集群开启xpack安全配置后无法启动 在配置文件中增加 xpack.security.enabled: true 后无法启动&#xff0c;日志中提示如下 Transport SSL must be enabled if security is enabled. Please set [xpack.security.transport.ssl.enabled] to [true] or disable security b…

qt配合映美精取图开发

最近开发一个项目&#xff0c;用映美精相机配合halcon做取图开发&#xff0c;由于网上资料小特意写个记录。到映美精官网下载驱动&#xff0c;映美精官网&#xff0c;下载映美精的工具开发包SDK 映美精的SDK下载SDK后找到classlib文件夹 里面就是SDK新建一个qt程序&#xff0c…

springboot yml文件数据源出现警告/报黄/数据库配置警告问题

1、看一下数据源的依赖是不是都引入完整了 2、看一下数据源是否有拼写错误 上图就是数据源拼写错误

手机上用什么方法可以切换ip

手机上用什么方法可以切换IP&#xff1f;在某些特定情境下&#xff0c;用户可能需要切换手机的IP地址&#xff0c;以满足网络安全、隐私保护或绕过地域限制等需求。下面以华为手机为例&#xff0c;将详细介绍手机IP地址切换的几种方法&#xff0c;帮助用户轻松实现这一目标。 一…

python可视化将多张图整合到一起(画布)

这周有点事忙着&#xff0c;没时间重温刚结束的Mathurcup数学建模&#xff0c;这两天也是再看了下&#xff0c;论文还是赶紧挺烂的&#xff0c;但比国赛又有进步&#xff08;说起国赛又不得不抱怨了&#xff0c;基本其余省份都发了&#xff0c;但江西......哎&#xff09;。哎&…

vue2,vue3,uniapp,小程序实现前端url生成二维码

最近遇到一个项目&#xff0c;api返回url地址&#xff0c;前端通过地址生成二维码。 话不多说直接上代码&#xff0c;亲测有效&#xff0c;希望能帮助大家&#xff0c;同时如果有更好的方法希望大家能够分享 1、第一步&#xff0c;在项目的utils文件夹下面创建一个weapp-qrco…

【韩老师零基础30天学会Java】01章

什么是程序&#xff1f; JavaEE企业版 示例1 public class Test{public static void main(String[] args){int res11;System.out.println("result"res);}}D:\Java\code>javac Test.javaD:\Java\code>java Test 缁撴灉2D:\Java\code>javac Test.javaD:\Java…

华为交换机实现不同VLAN内的互通配置(汇聚层设备作为网关)

背景如下&#xff1a; 如下图所示&#xff0c;PC1和PC2分别属于VLAN 2和VLAN 3&#xff0c;通过接入层设备DeviceB接入汇聚层设备DeviceA。PC3属于VLAN 4&#xff0c;通过接入层设备DeviceC接入汇聚层设备DeviceA。DeviceC不做任何配置&#xff0c;当做HUB即插即用。汇聚层设备…

SpringBoot旅游酒店系统源码免费分享+本地部署及介绍 - 幽络源

初步介绍 这是一套基于SpringBoot的旅游酒店系统&#xff0c;含有前台和后台&#xff0c;需要注意的是图片文件是存放于target中的&#xff0c;因此不建议删除这个临时目录。 原文及源码获取链接 > SpringBoot旅游酒店管理系统免费分享-幽络源 所需环境 JDK1.8、Maven、…

Java开发中的分布式锁使用教程

1. 基于ZooKeeper的分布式锁 1.1 引入依赖 在项目的pom.xml文件中添加以下依赖&#xff1a; <dependency><groupId>org.apache.curator</groupId><artifactId>curator-framework</artifactId><version>latest</version> </dep…

zynq pl设计中断问题

问题 逻辑工程师vivado工具生成的pl hdf文件后,通过xilinx的工具解析的的dts文件,会出现中断号异常的问题。 原始问题肯定是硬件表现为通讯异常,此处以网口为例,则网口不通。 网口查询 uboot下网口信息 如下命令查询到 两个mac下对应的phy,地址分别为4和6,和硬件设计一…

Scala 的包及其导入

Scala使用包来创建用于模块化程序的命名空间。通过在Scala文件的顶部声明一个或多个包名称可以创建包&#xff0c;另一种声明包的方式是使用0&#xff0c;这种方式可以嵌套包&#xff0c;并且提供更好的范围与封装控制。对于包的导入&#xff0c;Scala与Java的区别之一便是&…

【MySQL】关于MySQL启动后mysqld_safe和mysqld进程

在mysql服务器启动后&#xff0c;有2个进程mysqld_safe和mysqld,这是为啥&#xff1f; # ps -ef | grep mysqld root 6488 3324 0 Sep03 pts/0 00:00:00 /bin/sh /mysqlsoft/mysql/bin/mysqld_safe --defaults-file/etc/my.cnf --usermysql mysql 7327 6488 0 Sep03 pts/0 0…

Rust @绑定(Rust@绑定)(在模式匹配的同时将值绑定到变量)

文章目录 Rust中的绑定基础概念示例&#xff1a;基本模式匹配 绑定的使用示例&#xff1a;范围匹配并绑定变量 深入探索绑定的好处示例&#xff1a;复杂数据结构中的应用 总结 附加 Rust中的绑定 Rust 语言以其强类型系统和内存安全的特性著称。在进行模式匹配时&#xff0c;R…

数据结构-图的概念

不存在空图现象,顶点集不能为空,边集可以为空 研究链接一个顶点的边有多少条非常有意义 无向图的度边的二倍 有向图的入度出度,度边数 有向图一致 重点 子图必须联通,尽可能多的边和结点 对于一个生成树,他有n个节点就有n-1条边 修路问题将各个村庄相连,由于经费有限,只能选择…

黑马程序员Redis学习【持续更新】

Redis基础 一、Redis入门 1.Redis简介 【1】为什么学习Redis Redis是一个基于内存的key-value结构数据库。「Remote Dictionary Service」的首字母缩写&#xff0c;也就是「远程字典服务-remote dictionary server」。 基于内存存储&#xff0c;读写性能高适合存储热点数据…

利用SEO在4个月内打造每月940美元的导航站

在软件开发领域&#xff0c;SEO&#xff08;搜索引擎优化&#xff09;的潜力常常被人们低估&#xff0c;但它却能为网站增长带来显著效果。特别是在AI技术的加持下&#xff0c;你可以极大地加速流量增长并自动化大部分工作。本文将详细介绍一名Reddit用户如何在4个月内&#xf…