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

ops/2024/10/11 4:44:06/

给你一个整数数组 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,否则返回能组成该金额的最小硬币数。


http://www.ppmy.cn/ops/107388.html

相关文章

鸿蒙(API 12 Beta6版)GPU加速引擎服务【介绍与开发准备】

XEngine Kit&#xff08;GPU加速引擎服务&#xff09;提供基于马良GPU的性能提升方案&#xff0c;包括GPU/AI超分能力、自适应VRS、Subpass Shading等&#xff0c;通过图形算法以及软硬件优化&#xff0c;让用户拥有更高性能、更低功耗的3D游戏/应用、AR/VR体验。 场景介绍 优…

JS面试真题 part1

JS面试真题 part1 1、说说JavaScript中的数据类型&#xff0c;储存上的差别2、说说你了解的js数据结构3、DOM常见的操作有哪些4、说说你对BOM的理解&#xff0c;常见的BOM对象你了解哪些5、 和 区别&#xff0c;分别在什么情况使用 1、说说JavaScript中的数据类型&#xff0c;…

编写vue的输入框的自定义指令研究

先决条件&#xff0c;准备一个input和vue项目。这里使用了vue3项目。 <template><input> </template> 先确定自定义指令的编写方式。在setup里面直接编写。 <template><input v-input> </template><script setup> const vInput {mo…

随手记:小程序体积超出2M包大小如何优化

小程序的包体积限制是2M&#xff0c;超出包大小如何优化 先简单列出&#xff0c;最近比较忙&#xff0c;后续优化明细&#xff0c;有着急的先留言踢我 1.分包 留几个主要的页面体积小的&#xff0c;剩下的在page.json中拆到subpackages中&#xff0c;简单举个例子 "page…

node快速复制文件或文件夹,排除部分文件(node_modules)

const fs require(fs) const path require(path)/*** description: 获取完整的文件路径* param {*} url 路径* return {*} 返回完整的文件路径*/ const getPath (url) > {return path.join(__dirname, url) }/*** description: 获取参数* return {*} target【目标文件夹】…

MiniCPM-V: A GPT-4V Level MLLM on Your Phone

MiniCPM-V: A GPT-4V Level MLLM on Your Phone 研究背景和动机 现有的MLLM通常需要大量的参数和计算资源&#xff0c;限制了其在实际应用中的范围。大部分MLLM需要部署在高性能云服务器上&#xff0c;这种高成本和高能耗的特点&#xff0c;阻碍了其在移动设备、离线和隐私保…

【SpringBoot】使用Nacos服务注册发现与配置管理

前提&#xff1a;需要提前部署好nacos服务&#xff0c;这里可以参考我的文章&#xff1a;Windows下Nacos安装与配置 0. 版本信息 Spring Boot3.2.8Spring Cloud2023.0.1Spring Cloud alibaba2023.0.1.0nacos2.3.2本地安装的nacos2.3.0 Spring Boot、Spring Cloud、Spring Clo…

如何在算家云搭建OpenSora 1.2(文本生成视频)

一. OpenSora 1.2简介 1. 技术特点 高清视频生成 &#xff1a; OpenSora 1.2 在 720p 高清文生视频质量和生成时长上取得了突破性进展&#xff0c;支持无缝产出任意风格的高质量短片。通过引入视频压缩网络&#xff08;VAE&#xff09;和更优的扩散模型算法&#xff0c;显著…