动态规划—322. 零钱兑换
题目描述
前言介绍
零钱兑换 是一个经典的 动态规划 问题。给定一个整数数组 coins
,其中每个元素代表不同面额的硬币,再给定一个整数 amount
,表示需要凑成的总金额,要求计算出用这些硬币凑成该金额的最少硬币数量。如果凑不出该金额,则返回 -1。
基本思路
1. 问题定义
我们需要找到一个硬币组合,使得硬币的总和为 amount
,同时硬币的数量最少。如果无法凑出总金额,则返回 -1。
举例:
-
输入:
coins = [1, 2, 5]
,amount = 11
-
输出:
3
-
解释:11 = 5 + 5 + 1,最少需要 3 个硬币。
-
输入:
coins = [2]
,amount = 3
-
输出:
-1
-
解释:无法凑出总金额 3。
2. 理解问题和递推关系
动态规划思想:
这是一个典型的 完全背包问题。对于每一个金额 i
,我们需要找到一种组合,使得凑成 i
所需的硬币数量最少。
状态定义:
dp[i]
表示凑成金额i
所需的最少硬币数量。
状态转移方程:
- 对于每个硬币
coin
,如果coin <= i
,我们可以选择将该硬币加入到当前组合中,并更新状态:
[
dp[i] = \min(dp[i], dp[i - coin] + 1)
]- 其中
dp[i - coin]
表示凑成金额i - coin
所需的最少硬币数量,再加上当前使用的这一个硬币。
- 其中
边界条件:
- 当
amount = 0
时,不需要任何硬币即可凑成,故dp[0] = 0
。 - 初始时,将
dp
数组的所有元素设为无穷大(即无法凑出这些金额),然后逐步更新。
3. 解决方法
动态规划方法
- 初始化:创建一个大小为
amount + 1
的dp
数组,初始时dp[0] = 0
,其他位置的值为无穷大。 - 状态转移:遍历每个金额
i
,然后对每个硬币coin
,更新dp[i]
的值。 - 返回结果:如果
dp[amount]
依然是无穷大,说明无法凑出该金额,返回 -1;否则返回dp[amount]
。
伪代码:
initialize dp array with dp[0] = 0, and other dp[i] = inf
for each coin in coins:
for i from coin to amount:
dp[i] = min(dp[i], dp[i - coin] + 1)
if dp[amount] == inf:
return -1
else:
return dp[amount]
4. 进一步优化
- 时间复杂度:时间复杂度为
O(amount * n)
,其中n
是硬币的种类数,amount
是目标金额。 - 空间复杂度:空间复杂度为
O(amount)
,因为我们只需要维护一个大小为amount + 1
的dp
数组。
5. 小总结
- 递推思路:这是一个典型的动态规划问题,通过逐步构建
dp
数组,从最小的金额开始递推,计算出最终结果。 - 时间复杂度:时间复杂度为
O(amount * n)
,适合处理中等规模的输入。
以上就是零钱兑换问题的基本思路。
Python代码
class Solution:
def coinChange(self, coins: list[int], amount: int) -> int:
# 初始化dp数组,dp[i]表示凑成金额i的最少硬币数量
dp = [float('inf')] * (amount + 1)
dp[0] = 0 # 凑成金额0所需的硬币数量为0
# 动态规划计算
for coin in coins:
for i in range(coin, amount + 1):
dp[i] = min(dp[i], dp[i - coin] + 1)
# 如果dp[amount]还是inf,表示无法凑成目标金额
return dp[amount] if dp[amount] != float('inf') else -1
Python代码解释
- 初始化:定义
dp
数组,dp[i]
表示凑成金额i
的最少硬币数量,初始时所有金额都设为inf
,表示无法凑出。 - 动态规划递推:遍历每个硬币
coin
,更新dp[i]
,即计算出当前金额i
的最小硬币数。 - 返回结果:如果
dp[amount]
依然是无穷大,说明无法凑出目标金额,返回 -1;否则返回dp[amount]
。
C++代码
class Solution {
public:
int coinChange(vector<int>& coins, int amount) {
// 初始化dp数组,dp[i]表示凑成金额i的最少硬币数量
vector<int> dp(amount + 1, INT_MAX);
dp[0] = 0; // 凑成金额0所需的硬币数量为0
// 动态规划计算
for (int coin : coins) {
for (int i = coin; i <= amount; ++i) {
if (dp[i - coin] != INT_MAX) {
dp[i] = min(dp[i], dp[i - coin] + 1);
}
}
}
// 如果dp[amount]还是INT_MAX,表示无法凑成目标金额
return dp[amount] == INT_MAX ? -1 : dp[amount];
}
};
C++代码解释
- 初始化:定义
dp
数组,dp[i]
表示凑成金额i
的最少硬币数量,初始时所有金额都设为INT_MAX
,表示无法凑出。 - 动态规划递推:遍历每个硬币
coin
,更新dp[i]
,即计算出当前金额i
的最小硬币数。 - 返回结果:如果
dp[amount]
依然是INT_MAX
,说明无法凑出目标金额,返回 -1;否则返回dp[amount]
。
总结
- 核心思想:通过动态规划解决最少硬币数问题。
dp[i]
表示凑成金额i
所需的最少硬币数量,状态转移方程为dp[i] = min(dp[i], dp[i - coin] + 1)
。 - 时间复杂度:时间复杂度为
O(amount * n)
,其中n
是硬币的种类数,amount
是目标金额。 - 空间复杂度:空间复杂度为
O(amount)
,因为我们只需要维护一个dp
数组。