力扣第322题 零钱兑换 c++ java 动态规划
题目
中等
相关标签
给你一个整数数组 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
提示:
1 <= coins.length <= 12
1 <= coins[i] <= 231 - 1
0 <= amount <= 104
思路和解题方法
目标和的定义:这个问题的目标是计算凑出目标金额所需的最少硬币数量。
动态规划的思路:该代码使用了动态规划的思想,将原问题拆解为子问题,并利用已解决的子问题的解来求解更大规模的问题。
dp
数组的定义:代码创建了一个大小为amount+1
的dp
数组,用于保存计算中间状态的结果。dp[i]
表示组成金额i
所需的最少硬币数量。初始化:将
dp[0]
初始化为 0,表示组成金额为 0 不需要任何硬币。其他位置的dp
数组元素初始化为INT_MAX
,表示初始时无法凑出对应的金额。状态转移方程:采用两层循环来遍历物品和背包。外层循环遍历所有可用的硬币面额,内层循环遍历目标金额从该硬币面额开始到
amount
。这样可以逐步更新dp
数组,计算得到凑出每个目标金额所需的最少硬币数量。状态转移:对于当前的目标金额
j
,我们检查dp[j - coins[i]]
是否不是初始值INT_MAX
。如果不是初始值,则表示可以通过使用当前硬币面额coins[i]
得到目标金额j
。我们比较使用当前硬币和不使用当前硬币两种情况下所需的硬币数量,并取最小值作为dp[j]
的解。返回结果:最后,我们返回
dp[amount]
的值作为结果。如果dp[amount]
仍为初始值INT_MAX
,表示无法凑出目标金额,因此返回 -1。总结起来,这段代码使用动态规划的思想,通过构建一个
dp
数组来保存计算中间状态的结果。通过遍历物品和背包,并利用已解决子问题的解,逐步计算得到组成目标金额所需的最少硬币数量。最终,返回dp[amount]
的值作为结果。
复杂度
时间复杂度:
O(n * amount)
时间复杂度:
- 外层循环遍历硬币列表的长度,即
coins
的大小,所以时间复杂度为 O(n),其中 n 是硬币列表的长度。- 内层循环遍历目标金额
amount
,所以时间复杂度为 O(amount)。综合起来,总的时间复杂度为 O(n * amount)。
空间复杂度
O(amount)
空间复杂度:
- 创建了一个大小为
amount+1
的dp
数组,所以空间复杂度为 O(amount)。因此,该算法的空间复杂度为 O(amount)。
c++ 代码
class Solution {
public:
int coinChange(vector<int>& coins, int amount) {
vector<int> dp(amount + 1, INT_MAX); // 创建大小为 amount+1 的 dp 数组,初始值设置为 INT_MAX
dp[0] = 0; // 对于组成金额为 0 的情况,方法数为 0
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 - coins[i]] + 1, dp[j]); // 更新组成金额 j 的最小方法数
}
}
}
if (dp[amount] == INT_MAX) return -1; // 如果无法组成金额 amount,则返回 -1 表示无解
return dp[amount]; // 返回组成金额 amount 的最小方法数
}
};
vector<int> dp(amount + 1, INT_MAX);
:创建大小为 amount+1 的 dp 数组,用于保存组成不同金额的最小硬币数。初始值设置为 INT_MAX,表示初始状态下无解。dp[0] = 0;
:对于金额为 0 的情况,不需要使用任何硬币,所以最小硬币数为 0。for (int i = 0; i < coins.size(); i++)
:外层循环遍历硬币面额(物品),以便逐个考虑每个硬币的组合方式。for (int j = coins[i]; j <= amount; j++)
:内层循环遍历目标金额(背包),从当前硬币面额开始,直到目标金额 amount。这样可以确保只考虑能够达到的金额。if (dp[j - coins[i]] != INT_MAX)
:如果 dp[j - coins[i]] 不是初始值(即存在组合方式),则进入条件判断。dp[j] = min(dp[j - coins[i]] + 1, dp[j]);
:更新组成金额 j 的最小硬币数。在当前硬币面额 coins[i] 的情况下,组成金额 j 的最小硬币数为 dp[j - coins[i]] + 1 和当前 dp[j] 的较小值。if (dp[amount] == INT_MAX) return -1;
:如果无法组成金额 amount,则返回 -1 表示无解。return dp[amount];
:返回组成金额 amount 的最小硬币数。
Java代码
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时需要的硬币数目为0
dp[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];
}
}
觉得有用的话可以点点赞,支持一下。
如果愿意的话关注一下。会对你有更多的帮助。
每天都会不定时更新哦 >人< 。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)