752 字
4 分钟
零钱兑换

题目描述#

给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。

计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。

你可以认为每种硬币的数量是无限的。

解题思路#

  1. 动态规划
    • 定义 dp[i] 表示组成金额 i 所需的最小硬币数
    • 初始化 dp = 0,其他为极大值(表示不可达)
  2. 状态转移
    • 遍历每种金额,对每个硬币面额 coin
    • 若 i >= coin,则 dp[i] = min(dp[i], dp[i-coin] + 1)
  3. 边界处理
    • 金额为0时不需要任何硬币
    • 无法组合的金额返回-1
  4. 完全背包
    • 硬币可重复使用,类似完全背包问题
  5. 自底向上
    • 从小金额向大金额递推求解

关键洞察#

  1. 最优子结构
    • 大金额的最优解依赖小金额最优解
  2. 重叠子问题
    • 多个金额计算共享相同子问题
  3. 初始化技巧
    • 极大值设为 amount+1(最大硬币数上限)
  4. 剪枝优化
    • 硬币排序后提前终止无效计算
  5. 负数处理
    • 硬币面额需为正数,金额为正

复杂度分析#

指标说明
时间复杂度O(n×amount):n 为硬币种类数
空间复杂度O(amount):dp 数组大小
最优情况O(n×amount):必须填充整个 dp 表
最坏情况O(n×amount):同上

代码实现#

/**
* @param {number[]} coins
* @param {number} amount
* @return {number}
*/
var coinChange = function(coins, amount) {
const dp = new Array(amount+1).fill(amount+1);
dp[0]=0;
for(let i=1;i<=amount;i++){
for(const coin of coins){
if(coin<=i) {
dp[i] = Math.min(dp[i],dp[i-coin]+1)
}
}
}
return dp[amount] === amount+1 ? -1 : dp[amount]
};
pub fn coin_change(coins: Vec<i32>, amount: i32) -> i32 {
let amount = amount as usize;
let mut dp = vec![amount + 1; amount + 1];
dp[0] = 0;
for i in 1..=amount {
for &coin in &coins {
let coin = coin as usize;
if coin <= i {
dp[i] = dp[i].min(dp[i - coin] + 1);
}
}
}
if dp[amount] > amount {
-1
} else {
dp[amount] as i32
}
}
def coinChange(coins, amount):
dp = [amount + 1] * (amount + 1)
dp[0] = 0
for i in range(1, amount + 1):
for coin in coins:
if coin <= i:
dp[i] = min(dp[i], dp[i - coin] + 1)
return dp[amount] if dp[amount] <= amount else -1

实际应用场景#

  1. 自动售货机:计算最少硬币找零方案`
  2. 金融系统:债券最小拆分单元计算`
  3. 游戏设计:道具合成系统资源最小消耗`
  4. 物流装箱:最小包装箱数量计算`

变种:硬币组合总数#

def coinCombinations(coins, amount):
dp = [0] * (amount + 1)
dp[0] = 1 # 金额0有1种方案(不取)
for coin in coins:
for i in range(coin, amount + 1):
dp[i] += dp[i - coin]
return dp[amount]

相似题目#

零钱兑换
https://website-truelovings-projects.vercel.app/posts/algorithms/零钱兑换/
作者
欢迎来到StarSky的网站!
发布于
2025-09-03
许可协议
CC BY-NC-SA 4.0