零钱兑换问题 I

1、题目:力扣原题

2、分析

(1)结合我们之前分析的(动态规划解决背包问题),这里硬币有无限个对应完全背包问题。但又存在一点区别:纯完全背包是能否凑成总的金额,本题是要求凑成总金额的组合个数

(2)要注意是求解组合 还是排列 问题。例如 221 和121可以表示一种组合或者两种排列。组合之间不强调元素之间的顺序,而排列强调元素之间的顺序。

DP五部曲分析如下:

1)确定dp含义

dp[j]: 表示凑成总金额j可以得到的货币组合总数;

2)确定递推公式‘’

dp[j] (考虑coins[i]的组合总和) 就是所有的dp[j - coins[i]](不考虑coins[i])相加。

所以递推公式:dp[j] += dp[j - coins[i]];

求装满背包有几种方法,一般公式都是:dp[j] += dp[j - nums[i]];

3)初始化

dp[0]=1,表示凑成金额为0的货币组合数为1;

4)确定遍历顺序

这里就要根据上面讨论的来进行区别,题目是要求组合数还是排列数需要对遍历顺序进行处理。因为纯完全背包求得是能否凑成总和,和凑成总和的元素有没有顺序没关系,即:有顺序也行,没有顺序也行!

而本题要求凑成总和的组合数,元素之间要求没有顺序。所以纯完全背包是能凑成总和就行,不用管怎么凑的。本题是求凑出来的方案个数,且每个方案个数是为组合数。那么本题,两个for循环的先后顺序可就有说法了。

为了清晰对比,我们对两个for循环的先后遍历顺序讨论一下:

a、外层for循环遍历钱币, 内层for循环遍历金钱总额的情况

for (int i = 0; i < coins.size(); i++) { // 遍历物品(钱币数)for (int j = coins[i]; j <= amount; j++) { // 遍历背包容量(金钱总额),内层循环这里,背包容量必须大于物品大小才有意义,
//这个物品才可以装进背包,所以初始化j=coins[i]dp[j] += dp[j - coins[i]];}
}

因为金钱总额遍历在内循环,所以金钱总额里的每一个值只对应了钱币数的单次情况。例如,假设:coins[0] = 1,coins[1] = 2。

那么就是先把1加入计算,然后再把2加入计算,得到的方法数量只有{1, 2}这种情况。而不会出现{2, 1}的情况。所以这种先物品再背包的遍历顺序中dp[j]里计算的是组合数!

b、把两个for循环的遍历次序交换,先遍历金钱总额,再遍历钱币数

for (int j = 0; j <= amount; j++) { // 遍历背包容量(金钱总额)for (int i = 0; i < coins.size(); i++) { // 遍历物品(钱币)if (j - coins[i] >= 0) dp[j] += dp[j - coins[i]];}
}

背包容量的每一个值,都是经过 1 和 5 的计算,包含了{1, 5} 和 {5, 1}两种情况。先背包,在物品遍历,此时dp[j]里算出来的就是排列数!

为了进一步分析,我们采用dp五步曲中的第五步来展开讲解:

5)举例dp数组推导

输入: amount = 5, coins = [1, 2, 5] ,dp状态图如下:

我们采用先物品再背包的遍历顺序  来求解组合数:

最后 红色框中的结果就是最终的满足条件的组合数。

总结:

3、代码

java:

class Solution {public int change(int amount, int[] coins) {//递推表达式int[] dp = new int[amount + 1];//初始化dp数组,表示金额为0时只有一种情况,也就是什么都不装dp[0] = 1;for (int i = 0; i < coins.length; i++) {for (int j = coins[i]; j <= amount; j++) {dp[j] += dp[j - coins[i]];}}return dp[amount];}
}

python:

class Solution:def change(self, amount: int, coins: List[int]) -> int:dp = [0]*(amount + 1)dp[0] = 1# 遍历物品for i in range(len(coins)):# 遍历背包for j in range(coins[i], amount + 1):dp[j] += dp[j - coins[i]]return dp[amount]

----------------------------------------------------------------------------------------------------------------

零钱兑换问题 II

1、原题:力扣原题

和问题1的区别:问题1是求解满足条件的所有组合数,本题是要求凑成金额的最少硬币个数;其中问题1和问题2的硬币数量都是无限的,即是完全背包问题 的深入)

2、分析

根据题目要求,最简单的一种思路便是把满足硬币组合等于amount的组合全部列出来,然后找到组合数目最少的即可,可以用递归解决,但时间复杂度会很高,需要很好的剪枝策略;

另一个直观的想法便是采用动态规划,初始化一个amount+1大小的dp数组,记录每一个状态的最优解,过程如下:

1)dp定义

dp[j]: 表示 可以凑成金额为j的最少硬币组合个数;

2) dp递归公式

得到dp[j](考虑coins[i]),只有一个来源,dp[j - coins[i]](没有考虑coins[i])。

凑足总额为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)初始化

首先凑足总金额为0所需钱币的个数一定是0,那么dp[0] = 0;

其他下标对应的数值呢?

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

4)确定遍历顺序

故本题,任意顺序遍历都可,我们采用先遍历物品(硬币)再遍历背包(总金额);又因为硬币的个数有无数个,所以为完全背包问题,采用一维dp时,内层循环正序遍历即可

3)代码

class Solution {public int coinChange(int[] coins, int amount) {int max = Integer.MAX_VALUE;int[] dp = new int[amount + 1];//初始化dp数组为最大值for (int j = 0; j < dp.length; j++) {dp[j] = max;}//当金额为0时需要的硬币数目为0dp[0] = 0;for (int i = 0; i < coins.length; i++) {//正序遍历:完全背包每个硬币可以选择多次for (int j = coins[i]; j <= amount; j++) {//只有dp[j-coins[i]]不是初始最大值时,该位才有选择的必要if (dp[j - coins[i]] != max) {//选择硬币数目最小的情况dp[j] = Math.min(dp[j], dp[j - coins[i]] + 1);}}}return dp[amount] == max ? -1 : dp[amount];}
}

python:

class Solution:def coinChange(self, coins: List[int], amount: int) -> int:'''版本一'''# 初始化dp = [amount + 1]*(amount + 1)dp[0] = 0# 遍历物品for coin in coins:# 遍历背包for j in range(coin, amount + 1):dp[j] = min(dp[j], dp[j - coin] + 1)return dp[amount] if dp[amount] < amount + 1 else -1

动态规划——零钱兑换问题相关推荐

  1. 动态规划——零钱兑换(Leetcode 322)

    题目选自Leetcode 322.零钱兑换 想必大家看一眼就明白了(bushi),这就是动态规划的背包问题~ 算法思想 那么,既然知道了这是个动态规划问题,就要思考如何列出正确的状态转移方程? 1.确 ...

  2. 【算法题目】DFS BFS 动态规划 零钱兑换 Python

    322. 零钱兑换 给你一个整数数组 coins ,表示不同面额的硬币:以及一个整数 amount ,表示总金额.计算并返回可以凑成总金额所需的 最少的硬币个数 .如果没有任何一种硬币组合能组成总金额 ...

  3. python换零钱_python动态规划-零钱兑换

    零钱兑换 给定不同面额的硬币 coins 和一个总金额 amount.编写一个函数来计算可以凑成总金额所需的最少的硬币个数. 如果没有任何一种硬币组合能组成总金额,返回 -1. 示例 1: 输入: c ...

  4. 背包型动态规划——零钱兑换

    给定不同面额的硬币 coins 和一个总金额 amount.编写一个函数来计算可以凑成总金额所需的最少的硬币个数.如果没有任何一种硬币组合能组成总金额,返回 -1. 你可以认为每种硬币的数量是无限的. ...

  5. 刷题第45, 46天 | 70. 爬楼梯 (进阶)、322. 零钱兑换、279.完全平方数、139.单词拆分

    70. Climbing Stairs 题目链接:70. Climbing Stairs 思路链接:代码随想录动态规划-爬楼梯(进阶) 思路 思路 Code class Solution {publi ...

  6. 动态规划思路和Python解决零钱兑换问题和最大乘积子序列的乘积的问题

    动态规划(Dynamic Programming)思路和Python解题示例 动态规划是一种主要用来优化朴素递归的方法,每当输入不同值调用递归函数出现大量重复的(子)输入和调用(返回结果)时,就可以考 ...

  7. 代码随想录44——动态规划:完全背包理论基础、518零钱兑换II、377组合总和IV

    文章目录 1.完全背包理论基础 2.518零钱兑换II 2.1.题目 2.2.解答 3.377组合总和IV 3.1.题目 3.2.解答 4.组合和排列问题的便利顺序 4.1.组合问题 4.2.排列问题 ...

  8. 算法训练Day44 动态规划专题- 背包问题 | 完全背包基础知识;LeetCode518. 零钱兑换(装满背包有多少种方法,组合数);377.组合总和IV(装满背包有多少种方法,排列数)

    前言: 算法训练系列是做<代码随想录>一刷,个人的学习笔记和详细的解题思路,总共会有60篇博客来记录,计划用60天的时间刷完.  内容包括了面试常见的10类题目,分别是:数组,链表,哈希表 ...

  9. 【必备算法】动态规划:LeetCode题(六)322. 零钱兑换,518. 零钱兑换 II

    322. 零钱兑换² 给定不同面额的硬币 coins 和一个总金额 amount.编写一个函数来计算可以凑成总金额所需的最少的硬币个数.如果没有任何一种硬币组合能组成总金额,返回 -1. 示例 1: ...

最新文章

  1. github 与git 使用 及配置
  2. rmReport 自适应行高(自动行高)
  3. Mockito框架代码学习调试
  4. C#sql帮助类(登录查询界面)语句实例
  5. xilinx7中管脚mrcc和srcc_Xilinx 7系列FPGA架构之SelectIO结构(一)
  6. switch java 语法_Java_基础语法之switch语句
  7. OpenShift 4 - 基于URL的应用路由
  8. Docker学习总结(28)——Docker 容器健康检查机制
  9. 【二十一】插件开发——用于验证码识别的 JMeter 插件(上)
  10. 华为android10版本,华为手机助手(安卓版)最新手机版10.1.1.500
  11. 利用cookie爬取QQ邮箱的python脚本
  12. git push --set-upstream
  13. 12.1 LNMP架构介绍 12.2 MySQL安装 12.3/12.4 PHP安装 12.5 Ng
  14. systemtap打点方法
  15. SQLServer删除登录记录用户名和密码
  16. iOS 的keyChain
  17. 锐捷 linux共享wifi,电脑共享wifi都弱爆了,无线路由器直接共享锐捷
  18. java security md5_java自带的加密--java.security.MessageDigest(md5加密)
  19. Python 构建 Random Forest 和 XGBoost
  20. NRF2401使用详细说明

热门文章

  1. 前端行为层,表示层,结构层的分离
  2. 【R生态】R语言维恩图(Venn)集合图(Upset)的选择与绘制
  3. win10触屏输入法_IT之家学院:一招让Win10屏幕键盘回归经典
  4. a9g 开发环境搭建 windows
  5. 单调递增且值域非负的函数
  6. 30-【什么叫规矩 什么叫体统】deque容器
  7. 报警c语言程序,家庭防盗报警系统c语言程序(5页)-原创力文档
  8. COSMOS认证辅导,COSMOS认证和LOGO只限用于化妆品上
  9. 河南电视台:让中原文化“云”中起舞
  10. csol霸主永恒python_CSOL六一版本新福利 年神霸主免费用