代码随想录第四十五天 | 动态规划 完全背包的应用:爬楼梯改编;求最小个数,循环顺序无影响(322 零钱兑换,279 完全平方数)

news2024/12/25 12:41:41

1、爬楼梯改编

之前讲这道题目的时候,因为还没有讲背包问题,所以就只是讲了一下爬楼梯最直接的动规方法(斐波那契)
改为:一步一个台阶,两个台阶,三个台阶,…,直到 m个台阶。问有多少种不同的方法可以爬到楼顶

1阶,2阶,… m阶就是物品楼顶就是背包
每一阶可以重复使用,例如跳了1阶,还可以继续跳1阶,问跳到楼顶有几种方法其实就是问装满背包有几种方法。此时大家应该发现这就是一个完全背包问题
和昨天的题目动态规划:leetcode 377:组合总和 Ⅳ 基本就是一道题了

动规五部曲分析如下:
1、确定dp数组以及下标的含义
dp[i]爬到i个台阶楼顶,有dp[i]种方法

2、确定递推公式
在 leetcode 494:目标和 、 leetcode 518:零钱兑换II 、leetcode 377:组合总和 Ⅳ 中讲过了,求装满背包有几种方法,递推公式一般都是dp[i] += dp[i - nums[j]];
本题,dp[i]有几种来源dp[i - 1],dp[i - 2],dp[i - 3] 等等,即:dp[i - j],那么递推公式为:dp[i] += dp[i - j]

3、dp数组如何初始化
既然递归公式dp[i] += dp[i - j],那么dp[0] 一定为1dp[0]递归中一切数值基础所在,如果dp[0]是0的话,其他数值都是0
下标非0dp[i] 初始化为0,因为dp[i]靠dp[i-j]累计上来的,dp[i]本身为0这样才不会影响结果

4、确定遍历顺序
这是背包里求排列问题,即:1、2 步 和 2、1 步都是上三个台阶,但是这两种方法不一样,所以需将target放在外循环,将nums放在内循环
每一步可以走多次,这是完全背包内循环需要从前向后遍历

5、举例来推导dp数组
和 leetcode 377:组合总和 Ⅳ 几乎是一样的

代码随想录C++代码如下:

class Solution {
public:
    int climbStairs(int n) {
        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];
            }
        }
        return dp[n];
    }
};

注意 i, j 都是从1 开始

时间复杂度: O(nm)
空间复杂度: O(n)

2、求最小个数,循环顺序无影响

2.1 leetcode 322:零钱兑换

第一遍代码,也不知道应该用 排列 还是 组合
因为递归公式里面,求更小值初值取INT_MAX,就要避免在INT_MAX上再加数

class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        vector<int> dp(amount + 1, INT_MAX);//每种金额的最少硬币个数
        //求排列数,感觉组合数也行
        dp[0] = 0;//凑成他自己面额的最少要一个硬币
        for(int i = 0; i <= amount; i++) {
            for(int j = 0; j < coins.size(); j++) {
                if(i >= coins[j] && dp[i - coins[j]] != INT_MAX) {
                    //避免在INT_MAX上加一
                    dp[i] = min(dp[i], dp[i - coins[j]] + 1);
                }
            }
        }
        if(dp[amount] == INT_MAX) {
            return -1;
        }
        return dp[amount];
    }
};

代码随想录思路
题目中说每种硬币的数量无限的,可以看出是典型的完全背包问题

动规五部曲分析如下:
1、确定dp数组以及下标的含义
dp[j]凑足总额为j所需钱币最少个数为dp[j]

2、确定递推公式
凑足总额为j - coins[i]最少个数dp[j - coins[i]],那么只需要加上一个钱币coins[i]dp[j - coins[i]] + 1就是dp[j](考虑coins[i]),所以dp[j] 要取所有 dp[j - coins[i]] + 1 中最小
递推公式:dp[j] = min(dp[j - coins[i]] + 1, dp[j]);

3、dp数组如何初始化
首先凑足总金额为0所需钱币的个数一定是0,那么dp[0] = 0;

其他下标对应的数值呢?
考虑到递推公式的特性,dp[j]必须初始化为一个最大的数,否则就会在min(dp[j - coins[i]] + 1, dp[j])比较的过程中被初始值覆盖,所以下标非0的元素都是应该是最大值

代码如下:

vector<int> dp(amount + 1, INT_MAX);
dp[0] = 0;

4、确定遍历顺序
本题求钱币最小个数,那么钱币有顺序和没有顺序都可以,都不影响钱币的最小个数,所以本题并不强调集合是组合还是排列

如果求组合数就是外层for循环遍历物品,内层for遍历背包
如果求排列数就是外层for遍历背包,内层for循环遍历物品

所以本题的两个for循环的关系是:外层for循环遍历物品内层for遍历背包 或者 外层for遍历背包,内层for循环遍历物品都是可以的,本题钱币数量可以无限使用,那么是完全背包。所以遍历的内循环是正序

5、举例推导dp数组
以输入:coins = [1, 2, 5], amount = 5为例
举例推导dp数组
代码随想录C++ 代码如下:
采用coins放在外循环,target在内循环的方式
遍历顺序为:coins(物品)放在外循环,target(背包)在内循环。且内循环正序

// 版本一
class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        vector<int> dp(amount + 1, INT_MAX);
        dp[0] = 0;
        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 - coins[i]]是初始值则跳过
                    dp[j] = min(dp[j - coins[i]] + 1, dp[j]);
                }
            }
        }
        if (dp[amount] == INT_MAX) return -1;
        return dp[amount];
    }
};

时间复杂度: O(n * amount),其中 n 为 coins 的长度
空间复杂度: O(amount)

对于遍历方式为遍历背包放在外循环,遍历物品放在内循环也是可以的,见第一遍代码

2.2 leetcode 279:完全平方数

第一遍代码:
记录和为 n完全平方数最少数量求排列还是组合无所谓
dp[0] = 0;自己就是完全平方数的 肯定dp是1dp[i - j * j] == 0 时,dp[0] + 1

对于内层循环里的 <= n / 2
这里考虑到其实每个数的分解的加数 最大整数自乘的 那个整数 最大就是n / 2
但是因为n / 2是向下取整,所以当数为1的时候,<= 1/2直接把1也排除了,所以应该是n / 2 + 1

像代码随想录那样让 i * i <= n就不用整那么复杂了

class Solution {
public:
    int numSquares(int n) {
        vector<int> dp(n + 1, INT_MAX);
        //记录和为 n 的完全平方数的最少数量,求排列还是组合无所谓
        dp[0] = 0;//自己就是完全平方数的 肯定dp是1
        for(int i = 0; i <= n; i++) {
            for(int j = 1; j <= n / 2 + 1; j++) {
        /*
        这里考虑到其实每个数的分解的加数 最大整数自乘的 那个整数 最大就是n / 2
        但是因为n / 2是向下取整,所以当数为1的时候,<= 1/2直接把1也排除了,所以应该是n / 2 + 1
        */
                if(i - j * j >= 0) {
                    dp[i] = min(dp[i], dp[i - j * j] + 1);
                }
            }
        }
        return dp[n];
    }
};

代码随想录思路
完全平方数就是物品(可以无限件使用),凑个正整数n就是背包,问凑满这个背包最少有多少物品
就是个完全背包

动规五部曲分析如下:
1、确定dp数组(dp table)以及下标的含义
dp[j]和为j的完全平方数最少数量为dp[j]

2、确定递推公式
dp[j] 可以由dp[j - i * i]推出, dp[j - i * i] + 1 便可以凑成dp[j]
此时我们要选择最小的dp[j],所以递推公式dp[j] = min(dp[j - i * i] + 1, dp[j]);

3、dp数组如何初始化
dp[0]表示 和为0的完全平方数的最小数量,那么dp[0]一定是0
看题目描述,找到若干个完全平方数(比如 1, 4, 9, 16, …),题目描述中可没说要从0开始,dp[0]=0完全是为了递推公式

非0下标的dp[j] 应该是多少呢?
从递归公式dp[j] = min(dp[j - i * i] + 1, dp[j]);中可以看出每次dp[j]都要选最小的,所以非0下标的dp[j]一定要初始为最大值,这样dp[j]在递推的时候不会被初始值覆盖

4、确定遍历顺序
这是完全背包,如果求组合数就是外层for循环遍历物品,内层for遍历背包
如果求排列数就是外层for遍历背包,内层for循环遍历物品

在leetcode 322:零钱兑换 中就探讨了这个问题,本题也是一样的,是求最小数
所以本题外层for遍历背包,内层for遍历物品,还是外层for遍历物品,内层for遍历背包,都是可以的

5、举例推导dp数组
已输入n为5例,dp状态图如下:
举例推导dp数组
第一遍代码外层遍历背包,内层遍历物品;给出代码随想录给出的先遍历物品,再遍历背包的代码:

class Solution {
public:
    int numSquares(int n) {
        vector<int> dp(n + 1, INT_MAX);
        dp[0] = 0;
        for (int i = 1; i * i <= n; i++) { // 遍历物品
            for (int j = i * i; j <= n; j++) { // 遍历背包
                dp[j] = min(dp[j - i * i] + 1, dp[j]);
            }
        }
        return dp[n];
    }
};

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

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

相关文章

nanodet训练自己的数据集、NCNN部署到Android

nanodet训练自己的数据集、NCNN部署到Android 一、介绍二、训练自己的数据集1. 运行环境2. 数据集3. 配置文件4. 训练5. 训练可视化6. 测试 三、部署到android1. 使用官方权重文件部署1.1 下载权重文件1.2 使用Android Studio部署apk 2. 部署自己的模型【暂时存在问题】2.1 生成…

MCUXpresso GBK 汉字编码 lcd显示用

在上面添加 -fexec-charsetGBK &#xff0c;之后IDE就可以解码&#xff08;GBK&#xff09;汉字了

高防CDN与高防服务器:谁更胜一筹?

在当今数字化世界中&#xff0c;网络安全对于保护网站和应用程序至关重要。在这一背景下&#xff0c;高防CDN和高防服务器是两种流行的解决方案&#xff0c;用于应对不同类型的网络攻击。本文将分析高防CDN是否能够替代高防服务器&#xff0c;以及它们各自的优势和限制。 高防C…

数据库 高阶语句

目录 数据库 高阶语句 使用select 语句&#xff0c;用order by来对进行排序 区间判断查询和去重查询 如何对结果进行分组查询group by语句 limit 限制输出的结果记录&#xff0c;查看表中的指定行 通配符 设置别名&#xff1a;alias 简写就是 as 使用select 语句&#x…

用C++编写动画+音频版极乐净土

#define _CRT_SECURE_NO_WARNINGS #include<iostream> #include<graphics.h> #include<Windows.h> #include<MMSystem.h>//播放音乐所需要的头文件 #pragma comment(lib,"winmm.lib")//告诉编译器&#xff0c;加载winmm.lib库文件 #define C…

文件包含漏洞培训

CTF介绍 MISC(Miscellaneous)类型,即安全杂项,题目或涉及流量分析、电子取证、人肉搜索、数据分析等等。CRYPTO(Cryptography)类型,即密码学,题目考察各种加解密技术,包括古典加密技术、现代加密技术甚至出题者自创加密技术。PWN类型,PWN在黑客俚语中代表着攻破、取得权限…

项目管理之如何召开项目时间箱启动会议

时间箱启动会议是项目管理中至关重要的一环&#xff0c;它帮助项目团队明确目标、分配任务、制定计划&#xff0c;并为项目的顺利实施提供指导和支持。下边为大家介绍有关如何召开时间箱启动会议的一些建议&#xff1a; 确定会议目标 在召开时间箱启动会议前&#xff0c;首先…

分布式任务调度(02)--ElasticJob-Lite

1 简介 轻量级无中心化解决方案&#xff0c;jar包提供分布式任务的协调服务。 // 应用内部定义任务类&#xff0c;实现SimpleJob接口 public class MyElasticJob implements SimpleJob {// 编写自己任务的实际业务流程Overridepublic void execute(ShardingContext context) {…

Alert警告提示(antd-design组件库)简单使用

1.Alert警告提示 警告提示&#xff0c;展现需要关注的信息。 2.何时使用 当某个页面需要向用户显示警告的信息时。 非浮层的静态展现形式&#xff0c;始终展现&#xff0c;不会自动消失&#xff0c;用户可以点击关闭。 组件代码来自&#xff1a; 警告提示 Alert - Ant Design 3…

VM虚拟机逆向---羊城杯2023 vm_wo复现【详解】

文章目录 前言题目分析还原汇编exp后言 前言 兜兜转转终于回到这里了。之前复现了羊城杯较简单点的三题&#xff0c;到这一题vm卡住了&#xff0c;刷了好几道&#xff0c;终于搞定了。 题目分析 第一次遇到Mac os的题目&#xff0c;幸好还可以做。 opcode 和 指令操作 密文…

【Azure 架构师学习笔记】-Azure Storage Account(5)- Data Lake layers

本文属于【Azure 架构师学习笔记】系列。 本文属于【Azure Storage Account】系列。 接上文 【Azure 架构师学习笔记】-Azure Storage Account&#xff08;4&#xff09;- ADF 读取Queue Storage 前言 不管在云还是非云环境中&#xff0c; 存储是IT 系统的其中一个核心组件。在…

uni-app基于vue实现商城小程序

一、前言 参考“网易严选”小程序 项目采用传统vue项目结构&#xff0c;即uni-app打包和运行成小程序&#xff0c;使用HBuilder开发工具开发项目&#xff0c;通过运行启动“微信开发者工具”完成项目启动。 二、功能效果图 1.首页 2.分类 3.活动 4.我的 5.商品详情 6.购物车…

什么是AI算子开发

今天在某离职群里看到前同事聊天&#xff0c;说到国内某大厂的一个面试&#xff0c;本来求职面试的岗位是通信库&#xff0c;类似于英伟达的 nccl&#xff0c; 但是却被问到了很多与算子开发相关的问题。 看来算子开发岗位依然很稀缺。 联想到之前写过的一篇关于AI算子开发的文…

关闭EasyConnect进程详细步骤

1、不关闭导致的问题 nacos浏览器可以正常访问&#xff0c;但idea启动的时候连不上nacos&#xff0c;而且第二次启动都启动不了&#xff0c;一直卡在那里&#xff0c;排查了半天&#xff0c;怀疑是装的EasyConnect的VPN导致的&#xff0c;于是停止掉相关服务即可。但直接结束进…

Ubuntu中安装rabbitMQ

一、安装 RabbitMQ ①&#xff1a;更新源 sudo apt-get update②&#xff1a;安装Rrlang语言 由于RabbitMq需要erlang语言的支持&#xff0c;在安装RabbitMq之前需要安装erlang sudo apt-get install erlang-nox③&#xff1a;安装rabbitMQ sudo apt-get install rabbitmq-s…

【Proteus仿真】【Arduino单片机】数码管显示

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用Proteus8仿真Arduino单片机控制器&#xff0c;使用TM1637、共阳数码管等。 主要功能&#xff1a; 系统运行后&#xff0c;数码管显示数字、字符。 二、软件设计 /* 作者&#xff1a;嗨小易&am…

如何手动获取spring/springboot中的IOC容器(全局上下文对象)?

IDE&#xff1a;IntelliJ IDEA 2022.2.3 x64 操作系统&#xff1a;win10 x64 位 家庭版 JDK: 1.8 文章目录 前言一、如何手动获取spring容器[ApplicationContext]&#xff1f;方式①&#xff1a;在启动类中获取spring容器方式②&#xff1a;自定义工具类实现ServletContextList…

6.1二叉树的递归遍历(LC144,LC15,LC94)

什么是递归函数&#xff1f; 递归函数是一种函数调用自身的编程技巧。 在递归函数中&#xff0c;函数通过不断调用自身来解决一个问题&#xff0c;直到达到基本情况&#xff08;递归终止条件&#xff09;并返回结果。 递归函数在解决一些问题时非常有用&#xff0c;特别是那些…

kmp算法详解+next数组求解

KMP算法是一种高效的字符串匹配算法&#xff0c;其核心思想是利用已经匹配的部分信息&#xff0c;尽可能减少匹配次数。KMP算法通过预处理阶段计算一个最大前后缀长度数组&#xff08;也称为“部分匹配表”或“失败函数”&#xff09;&#xff0c;用于在匹配失败时确定模式串应…

互联网Java工程师面试题·Spring篇·第七弹

目录 36、什么是基于 Java 的 Spring 注解配置? 给一些注解的例子. 37、什么是基于注解的容器配置? 38、怎样开启注解装配&#xff1f; 39、Required 注解 40、Autowired 注解 41、Qualifier 注解 42、在 Spring 框架中如何更有效地使用 JDBC? 43、JdbcTemplate 44…