【LeetCode动态规划#09】完全背包问题实战,其二(零钱兑换和完全平方数--求物品放入个数)
零钱兑换
给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
你可以认为每种硬币的数量是无限的。
示例 1:
- 输入:coins = [1, 2, 5], amount = 11
- 输出:3
- 解释:11 = 5 + 5 + 1
示例 2:
- 输入:coins = [2], amount = 3
- 输出:-1
示例 3:
- 输入:coins = [1], amount = 0
- 输出:0
示例 4:
- 输入:coins = [1], amount = 1
- 输出:1
示例 5:
- 输入:coins = [1], amount = 2
- 输出:2
提示:
- 1 <= coins.length <= 12
- 1 <= coins[i] <= 2^31 - 1
- 0 <= amount <= 10^4
思路
硬币可以无限次使用,典型的完全背包问题(正序遍历)
注意这次是要求放入硬币的个数,最少个数
不多说直接看
五步走
1、确定dp数组的含义
dp[j]:能够凑出总金额j的最少硬币数
2、确定递推公式
如何推导出dp[j]? 这里还是可以分为放入硬币和不放入硬币两种情况(详见)
(1)不放入
如果不放入硬币,那么就还是取上一层遍历递推的结果,即dp[j] (体现用一维数组解决背包问题时,数组的“滚动”,可以联系 零钱兑换II 的解释)
即dp[j] = dp[j];
(2)放入硬币
假设我们现在遍历到j - coins[i]的容量,此时能凑出总金额j - coins[i]的最少硬币数是dp[j - coins[i]]
我们要求的是总金额为j(即容量为j)的最少硬币数,显然只需j - coins[i] + coins[i]即可,也就是再放一个硬币进背包。
那么相当于此时遍历到了背包容量j,又因为硬币放入背包时要占空间的,所以背包容量还是可以表示为j - coins[i]
此时,能凑出总金额j的最少硬币数是dp[j - coins[i]] + 1,加的1是指放入的这个硬币
即dp[j] = dp[j - coins[i]] + 1;
由于我们求的是最少硬币数,因此需要取这两者中更小的那个,那么递推公式就是
dp[j] = min(dp[j], dp[j - coins[i]] + 1);
3、初始化dp数组
这里不要惯性思维直接把dp[0]初始化为1,先看看题目
题目给的示例中明确了,dp[0] = 0
那其他位置呢?因为我们要找的的是最少硬币数,也就是递推过程中的最小值
为了不影响最小值的更新,应该将其余部分的值初始化为最大整数INT_MAX
4、确定遍历顺序
本题求钱币最小个数,那么钱币有顺序和没有顺序都可以,都不影响钱币的最小个数。
所以本题并不强调集合是组合还是排列。
代码
class Solution {
public:
int coinChange(vector<int>& coins, int amount) {
//定义dp数组并初始化
//因为要找的是更小的递推值,所以初始值应该设为最大值
vector<int> dp(amount + 1, INT_MAX);
//由题目给的示例可知,dp[0]时应该是0
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 - coins[i]]是初始值则跳过
dp[j] = min(dp[j], dp[j - coins[i]] + 1);//注意这里找的是硬币个数
}
}
}
//如果递推公式推导最后发现最后的值还是初始值,说明没有找到组合,返回-1
if (dp[amount] == INT_MAX) return -1;
return dp[amount];
}
};
完全平方数
给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, ...)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。
给你一个整数 n ,返回和为 n 的完全平方数的 最少数量 。
完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,1、4、9 和 16 都是完全平方数,而 3 和 11 不是。
示例 1:
- 输入:n = 12
- 输出:3
- 解释:12 = 4 + 4 + 4
示例 2:
- 输入:n = 13
- 输出:2
- 解释:13 = 4 + 9
提示:
- 1 <= n <= 10^4
思路
"若干个完全平方数"---物品
"给定正整数 n"---背包容量
又由示例1可知,物品可以重复使用,因此这是一个完全背包
五步走
1、确定dp数组含义
dp[j]:给定整数j时,能放入使和为j的完全平方数的最少数量是dp[j]
2、确定递推公式
如何推导出dp[j]?
当然还是从两个方向:加上当前的完全平方数或者不加
所以dp[j] = min(dp[j], dp[j - i * i] + 1);这里求的是数量,所以仍然是加1
注意,所谓的"完全平方数"就是整数的平方,每个数的平方都是"完全平方数"
本题是要用"完全平方数"来装背包,所以好获取"完全平方数",只需在背包容量上限内不断循环一个自增的值,然后取平方即可(i*i)
3、初始化dp数组
联系dp定义,dp[0]:给定整数0时,能放入使和为j的完全平方数的最少数量是dp[0]
感觉dp[0]时应该是没有能放进去的(这里理由还没想好,以后可能有补充),并且题目是从1开始给的整数,就没有0这种情况
那么暂定dp[0] = 0(不行再改1),然后其余部分分的初始化还是INT_MAX(原因见上一题)
4、确定遍历顺序
完全背包,正序遍历
然后因为不是求排列或组合,遍历物品和容量时的顺序可以调换
代码
class Solution {
public:
int numSquares(int n) {
//定义dp数组
vector<int> dp(n + 1, INT_MAX);
//初始化
dp[0] = 0;
//遍历dp数组
for(int i = 1; i*i <= n; ++i){//遍历物品
for(int j = i*i; j <= n; ++j){//遍历背包容量,
dp[j] = min(dp[j], dp[j - (i * i)] + 1);
}
}
return dp[n];
}
};
在代码实现中要注意两层for循环的处理细节
这里我们用来放入的“物品”是"完全平方数",也就是i*i
不像之前的题,我们从一个数组之类的地方把物品取出来放入就行
这里的物品需要使用循环值来“现用现算”
对于i*i <= n,其实就相当于之前的j = coins[i]; j <= amount
第二层for循环也是同理,将i*i这个整体看成物品即可
【LeetCode动态规划#09】完全背包问题实战,其二(零钱兑换和完全平方数--求物品放入个数)的更多相关文章
- 【LeetCode动态规划#05】背包问题的理论分析(基于代码随想录的个人理解,多图)
背包问题 问题描述 背包问题是一系列问题的统称,具体包括:01背包.完全背包.多重背包.分组背包等(仅需掌握前两种,后面的为竞赛级题目) 下面来研究01背包 实际上即使是最经典的01背包,也不会直接出 ...
- 【LeetCode动态规划#08】完全背包问题实战与分析(零钱兑换II)
零钱兑换II 力扣题目链接(opens new window) 给定不同面额的硬币和一个总金额.写出函数来计算可以凑成总金额的硬币组合数.假设每一种面额的硬币有无限个. 示例 1: 输入: amoun ...
- 【LeetCode动态规划#07】01背包问题一维写法(状态压缩)实战,其二(目标和、零一和)
目标和(放满背包的方法有几种) 力扣题目链接(opens new window) 难度:中等 给定一个非负整数数组,a1, a2, ..., an, 和一个目标数,S.现在你有两个符号 + 和 -.对 ...
- 【LeetCode动态规划#10】完全背包问题实战,其三(单词拆分,涉及集合处理字符串)
单词拆分 力扣题目链接(opens new window) 给定一个非空字符串 s 和一个包含非空单词的列表 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词. 说明: 拆 ...
- 【LeetCode动态规划#06】分割等和子集(01背包问题一维写法实战)
分割等和子集 分割等和子集 给你一个 只包含正整数 的 非空 数组 nums .请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等. 示例 1: 输入:nums = [1,5,11,5 ...
- [LeetCode] 动态规划入门题目
最近接触了动态规划这个厉害的方法,还在慢慢地试着去了解这种思想,因此就在LeetCode上面找了几道比较简单的题目练了练手. 首先,动态规划是什么呢?很多人认为把它称作一种"算法" ...
- LeetCode:零钱兑换【322】【DP】
LeetCode:零钱兑换[322][DP] 题目描述 给定不同面额的硬币 coins 和一个总金额 amount.编写一个函数来计算可以凑成总金额所需的最少的硬币个数.如果没有任何一种硬币组合能组成 ...
- LeetCode:322. 零钱兑换
链接:https://leetcode-cn.com/problems/coin-change/ 标签:动态规划.完全背包问题.广度优先搜索 题目 给定不同面额的硬币 coins 和一个总金额 amo ...
- leetcode动态规划题目总结
Hello everyone, I am a Chinese noob programmer. I have practiced questions on leetcode.com for 2 yea ...
- 【动态规划】简单背包问题II
问题 B: [动态规划]简单背包问题II 时间限制: 1 Sec 内存限制: 64 MB提交: 21 解决: 14[提交][状态][讨论版] 题目描述 张琪曼:“为什么背包一定要完全装满呢?尽可能 ...
随机推荐
- log4j-漏洞修复
Log4j漏洞修复 修复参考文档:https://www.cert.org.cn/publish/main/9/2021/20211215154225883558274/202112151542258 ...
- 数据结构与算法 第二章线性表(48课时课程笔记)Data Structure and Algorithms
2.1 线性表的类型定义 一个线性表是n个数据元素的有限序列. (1)结构初始化 InitList(&L) 构造一个空的线性表L. (2)销毁结构 DestroyList(&L) (3 ...
- MySQL查询排序和分页
连接数据库 mysql -hlocalhost -uroot -proot 排序查询语法: select 字段列表 from 表名 order by 字段1 排序方式1, 字段3 排序方式2,字段3 ...
- Vite 按需引入 Ant Design Vue 3.0
Vite 按需引入 Ant Design Vue 3.0 第一步下载: npm i unplugin-vue-components -D 需要注意的是:Vite你可以用 unplugin-vue-co ...
- 【学到了】golang的[]byte可以append string类型的数据
上代码: func Test_use_string(t *testing.T){ arr := make([]byte,0, 100) arr = append(arr, "abcd&quo ...
- easyui 使用不同的url以获取不同数据源信息
转载 https://www.bbsmax.com/A/kjdw1x06JN/ https://blog.csdn.net/lixinhui199/article/details/50724081 参 ...
- c++基础之函数
距离上次更新又过了一周,又该更新新的读书笔记了.本次更新的主要是c++中函数部分的内容 c++ 中的函数与c语言中的函数大致用法或者语法是一样的,这里就不就这点详细展开了.需要注意的是c/c++中并没 ...
- 手撕Vue-数据驱动界面改变上
经过上一篇的介绍,已经实现了监听数据的变化,接下来就是要实现数据变化后,界面也跟着变化,这就是数据驱动界面改变. 想要实现数据变化之后更新UI界面,我们可以使用发布订阅模式来实现,先定义一个观察者类, ...
- RestTemplate-postForObject详解、调用Https接口、源码解析,读懂这一篇文章就够了
restTemplate 目录 restTemplate 1. 基本介绍 2. 常用方法分析及举例 2.1. get请求 2.2. post请求 3. springboot中使用restTemplat ...
- 4.4 Windows驱动开发:内核监控进程与线程创建
当你需要在Windows操作系统中监控进程的启动和退出时,可以使用PsSetCreateProcessNotifyRoutineEx函数来创建一个MyCreateProcessNotifyEx回调函数 ...