零钱兑换

力扣题目链接(opens new window)

给定不同面额的硬币 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];
}
};

完全平方数

力扣题目链接(opens new window)

给定正整数 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】完全背包问题实战,其二(零钱兑换和完全平方数--求物品放入个数)的更多相关文章

  1. 【LeetCode动态规划#05】背包问题的理论分析(基于代码随想录的个人理解,多图)

    背包问题 问题描述 背包问题是一系列问题的统称,具体包括:01背包.完全背包.多重背包.分组背包等(仅需掌握前两种,后面的为竞赛级题目) 下面来研究01背包 实际上即使是最经典的01背包,也不会直接出 ...

  2. 【LeetCode动态规划#08】完全背包问题实战与分析(零钱兑换II)

    零钱兑换II 力扣题目链接(opens new window) 给定不同面额的硬币和一个总金额.写出函数来计算可以凑成总金额的硬币组合数.假设每一种面额的硬币有无限个. 示例 1: 输入: amoun ...

  3. 【LeetCode动态规划#07】01背包问题一维写法(状态压缩)实战,其二(目标和、零一和)

    目标和(放满背包的方法有几种) 力扣题目链接(opens new window) 难度:中等 给定一个非负整数数组,a1, a2, ..., an, 和一个目标数,S.现在你有两个符号 + 和 -.对 ...

  4. 【LeetCode动态规划#10】完全背包问题实战,其三(单词拆分,涉及集合处理字符串)

    单词拆分 力扣题目链接(opens new window) 给定一个非空字符串 s 和一个包含非空单词的列表 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词. 说明: 拆 ...

  5. 【LeetCode动态规划#06】分割等和子集(01背包问题一维写法实战)

    分割等和子集 分割等和子集 给你一个 只包含正整数 的 非空 数组 nums .请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等. 示例 1: 输入:nums = [1,5,11,5 ...

  6. [LeetCode] 动态规划入门题目

    最近接触了动态规划这个厉害的方法,还在慢慢地试着去了解这种思想,因此就在LeetCode上面找了几道比较简单的题目练了练手. 首先,动态规划是什么呢?很多人认为把它称作一种"算法" ...

  7. LeetCode:零钱兑换【322】【DP】

    LeetCode:零钱兑换[322][DP] 题目描述 给定不同面额的硬币 coins 和一个总金额 amount.编写一个函数来计算可以凑成总金额所需的最少的硬币个数.如果没有任何一种硬币组合能组成 ...

  8. LeetCode:322. 零钱兑换

    链接:https://leetcode-cn.com/problems/coin-change/ 标签:动态规划.完全背包问题.广度优先搜索 题目 给定不同面额的硬币 coins 和一个总金额 amo ...

  9. leetcode动态规划题目总结

    Hello everyone, I am a Chinese noob programmer. I have practiced questions on leetcode.com for 2 yea ...

  10. 【动态规划】简单背包问题II

    问题 B: [动态规划]简单背包问题II 时间限制: 1 Sec  内存限制: 64 MB提交: 21  解决: 14[提交][状态][讨论版] 题目描述 张琪曼:“为什么背包一定要完全装满呢?尽可能 ...

随机推荐

  1. log4j-漏洞修复

    Log4j漏洞修复 修复参考文档:https://www.cert.org.cn/publish/main/9/2021/20211215154225883558274/202112151542258 ...

  2. 数据结构与算法 第二章线性表(48课时课程笔记)Data Structure and Algorithms

    2.1 线性表的类型定义 一个线性表是n个数据元素的有限序列. (1)结构初始化 InitList(&L) 构造一个空的线性表L. (2)销毁结构 DestroyList(&L) (3 ...

  3. MySQL查询排序和分页

    连接数据库 mysql -hlocalhost -uroot -proot 排序查询语法: select 字段列表 from 表名 order by 字段1 排序方式1, 字段3 排序方式2,字段3 ...

  4. Vite 按需引入 Ant Design Vue 3.0

    Vite 按需引入 Ant Design Vue 3.0 第一步下载: npm i unplugin-vue-components -D 需要注意的是:Vite你可以用 unplugin-vue-co ...

  5. 【学到了】golang的[]byte可以append string类型的数据

    上代码: func Test_use_string(t *testing.T){ arr := make([]byte,0, 100) arr = append(arr, "abcd&quo ...

  6. easyui 使用不同的url以获取不同数据源信息

    转载 https://www.bbsmax.com/A/kjdw1x06JN/ https://blog.csdn.net/lixinhui199/article/details/50724081 参 ...

  7. c++基础之函数

    距离上次更新又过了一周,又该更新新的读书笔记了.本次更新的主要是c++中函数部分的内容 c++ 中的函数与c语言中的函数大致用法或者语法是一样的,这里就不就这点详细展开了.需要注意的是c/c++中并没 ...

  8. 手撕Vue-数据驱动界面改变上

    经过上一篇的介绍,已经实现了监听数据的变化,接下来就是要实现数据变化后,界面也跟着变化,这就是数据驱动界面改变. 想要实现数据变化之后更新UI界面,我们可以使用发布订阅模式来实现,先定义一个观察者类, ...

  9. RestTemplate-postForObject详解、调用Https接口、源码解析,读懂这一篇文章就够了

    restTemplate 目录 restTemplate 1. 基本介绍 2. 常用方法分析及举例 2.1. get请求 2.2. post请求 3. springboot中使用restTemplat ...

  10. 4.4 Windows驱动开发:内核监控进程与线程创建

    当你需要在Windows操作系统中监控进程的启动和退出时,可以使用PsSetCreateProcessNotifyRoutineEx函数来创建一个MyCreateProcessNotifyEx回调函数 ...