当前位置: 首页 > news >正文

【动态规划】【完全背包】力扣322. 零钱兑换

给你一个整数数组 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
在这里插入图片描述
法一:回溯+记忆化搜索

class Solution {vector<int> count;int dp(vector<int>& coins, int rem) {if(rem == 0) return 0;if(rem < 0) return -1;if(count[rem-1] != 0) return count[rem-1];int Min = INT_MAX;for(int coin : coins){int res = dp(coins, rem - coin);if(res >= 0 && res < Min){Min = res + 1;}}count[rem-1] = Min == INT_MAX ? -1 : Min;return count[rem-1];}
public:int coinChange(vector<int>& coins, int amount) {if(amount == 0){return 0;}count.resize(amount);return dp(coins, amount);}
};

时间复杂度:O(Sn),其中 S 是金额,n 是面额数。我们一共需要计算 S 个状态的答案,且每个状态 F(S) 由于上面的记忆化的措施只计算了一次,而计算一个状态的答案需要枚举 n 个面额值,所以一共需要 O(Sn) 的时间复杂度。
空间复杂度:O(S),我们需要额外开一个长为 S 的数组来存储计算出来的答案 F(S) 。

回溯部分:

int dp(vector<int>& coins, int rem) {if(rem == 0) return 0;if(rem < 0) return -1;if(count[rem-1] != 0) return count[rem-1];int Min = INT_MAX;for(int coin : coins){int res = dp(coins, rem - coin);if(res >= 0 && res < Min){Min = res + 1;}}count[rem-1] = Min == INT_MAX ? -1 : Min;return count[rem-1];}

我们定义一个大小在主函数coinChange中定义为amount的数组count来记忆化,减少重复运算额。首先dp的定义是,coins也就是可选择的coins,rem也就是目标金额,也就是说dp(coins, rem)的含义就是,要凑成rem金额,最少需要多少枚硬币。

假设我们在主函数coinChange传入树的头结点dp(coins, amount),那么会发生什么过程?首先dp要计算最少需要多少枚硬币才能凑到目标金额,这时候他就会枚举如果拿了coin金额的一个硬币,那么只要再凑rem-coin金额即可。那么要计算dp(coins, rem),不就是dp(coins, rem - coin) + 1吗,反过来想就是凑够rem - coin金额需要dp(coins, rem-coin)个硬币,那么再拿一个面值为coin的硬币,就可以凑成rem金额,然后硬币数量+1就是最小金额。但是由于dp(coins,rem-coin)由许多种情况构成,coin可以是不同面额,那么为了凑成价值为rem金额的最小硬币数量,就要求coin要满足dp(coins,rem-coin)必须是所有coin情况的最小的硬币数量,这时候再拿一个价值为coin的硬币,才是dp(coins,rem)的最小硬币组合数。这也就是为什么我们要遍历coins中每一个coin的情况,然后才能找到最小的res是多少,然后让他+1 = Min(注意:Min是局部变量,在每个递归中都存在,互不影响)。

有人会好奇那么不断找到最后,会发生什么,当rem == 0的时候,也就是末节点,实际上的含义也就是凑成金额0需要0种组合,这时候他的父节点就会计算res为0,然后记录Min为res+1 = 1。还有一种情况是rem最后小于0,那么说明这种组合是不合理的,这样子,这样一个路径上的coin金额加起来会大于你的目标amount,所以返回-1来说明这种情况不合理,这时候父节点的res由于小于0,则不会考虑记录到Min中。

最后也就是记忆每一种rem的金额的最小硬币数,下次计算金额为rem的dp,就会直接返回count中储存的结果。

法二:动态规划

class Solution {
public:int coinChange(vector<int>& coins, int amount) {vector<int>dp(amount+1, amount+1);dp[0] = 0;for(int i = 1; i <= amount; i++){for(int j = 0; j < coins.size(); j++){if(coins[j] <= i){dp[i] = min(dp[i], dp[i-coins[j]]+1);}}}return dp[amount] == amount + 1 ? -1 : dp[amount];}
};

时间复杂度:O(Sn),其中 S 是金额,n 是面额数。我们一共需要计算 O(S) 个状态,S 为题目所给的总金额。对于每个状态,每次需要枚举 n 个面额来转移状态,所以一共需要 O(Sn) 的时间复杂度。
空间复杂度:O(S)。数组 dp 需要开长度为总金额 S 的空间。

首先是初始化一个大小为amount+1的数组,并初始化所有元素为amount + 1,表示一个不可能的高值,用来初始化 dp 数组。因为不可能需要超过 amount 个硬币来凑成 amount,所以用 amount + 1 来初始化表示不可能的解。

然后遍历从 1 到 amount 的每个金额 i,试图找到每个金额 i 需要的最少硬币数。
内层循环:for (int j = 0; j < (int)coins.size(); ++j),遍历每个硬币面额 coins[j],如果 coins[j] <= i,就可以尝试使用这个硬币凑出金额 i。
dp[i - coins[j]] + 1:表示在凑成金额 i - coins[j] 的基础上再加上一个面额为 coins[j] 的硬币,来凑出金额 i。
dp[i] = min(dp[i], dp[i - coins[j]] + 1):更新 dp[i],取当前 dp[i] 和 dp[i - coins[j]] + 1 的较小值,表示凑出金额 i 所需的最少硬币数。

最后如果dp[amount]没有被更新,返回-1,否则返回能组成该金额的最小硬币数。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • Java数据结构(八)——插入排序、希尔排序
  • 【超简单】1分钟解决ppt全文字体一键设置
  • es数组包含查询
  • 10.2 TCP IP模型、IP协议、IPv4、子网掩码
  • CAS与原子操作
  • 自动化部署代码【gitlab jenkins 华为云】
  • 【2024高教社杯国赛C题】数学建模国赛建模过程+完整代码论文全解全析
  • 2409wtl,wtl与ddx
  • vscode从本地安装插件
  • 数据集 Ubody人体smplx三维建模mesh-姿态估计 >> DataBall
  • Win10磁盘出现小锁和感叹号的解决办法
  • Nexus配置npm私服
  • 深度学习(九)-图像形态操作
  • UDP通信实现
  • 有希带你深入理解指针(4)
  • C++类的相互关联
  • exif信息对照
  • Linux编程学习笔记 | Linux多线程学习[2] - 线程的同步
  • mysql innodb 索引使用指南
  • nodejs调试方法
  • Sass Day-01
  • 分布式任务队列Celery
  • 工程优化暨babel升级小记
  • 观察者模式实现非直接耦合
  • 深度学习入门:10门免费线上课程推荐
  • 手写双向链表LinkedList的几个常用功能
  • ​2020 年大前端技术趋势解读
  • ​Base64转换成图片,android studio build乱码,找不到okio.ByteString接腾讯人脸识别
  • ​浅谈 Linux 中的 core dump 分析方法
  • ​人工智能之父图灵诞辰纪念日,一起来看最受读者欢迎的AI技术好书
  • #《AI中文版》V3 第 1 章 概述
  • #WEB前端(HTML属性)
  • #Z0458. 树的中心2
  • ( )的作用是将计算机中的信息传送给用户,计算机应用基础 吉大15春学期《计算机应用基础》在线作业二及答案...
  • (1)SpringCloud 整合Python
  • (2)STL算法之元素计数
  • (javaweb)Http协议
  • (附源码)apringboot计算机专业大学生就业指南 毕业设计061355
  • (一)基于IDEA的JAVA基础10
  • (原創) 如何動態建立二維陣列(多維陣列)? (.NET) (C#)
  • .NET Framework .NET Core与 .NET 的区别
  • .net wcf memory gates checking failed
  • .NET单元测试
  • .net中我喜欢的两种验证码
  • .vimrc php,修改home目录下的.vimrc文件,vim配置php高亮显示
  • @requestBody写与不写的情况
  • @SentinelResource详解
  • @Transactional 详解
  • [ Linux ] git工具的基本使用(仓库的构建,提交)
  • [ 蓝桥杯Web真题 ]-Markdown 文档解析
  • [.net] 如何在mail的加入正文显示图片
  • [202209]mysql8.0 双主集群搭建 亲测可用
  • [Algorithm][综合训练][体育课测验(二)][合唱队形][宵暗的妖怪]详细讲解
  • [Android]常见的数据传递方式
  • [docker] Docker的私有仓库部署——Harbor