问题描述
给定一个整数数组 coins 表示不同面值的硬币,以及一个整数 amount 表示需要凑出的总金额。编写一个函数 solution,返回能够凑出 amount 所需的最少硬币数量。如果无法凑出该金额,则返回空数组。
例如:
- 输入:
coins = [1, 2, 5],amount = 18 - 输出:
[5, 5, 5, 2, 1] - 解释:使用三个 5 面值的硬币,一个 2 面值的硬币和一个 1 面值的硬币,总共五个硬币。
动态规划思路
1. 状态定义
我们定义 dp[i] 表示凑出金额 i 所需的最少硬币数量。初始状态下,dp[0] = 0,因为凑出金额 0 不需要任何硬币。其他 dp[i] 初始化为一个较大的值(如 amount + 1),表示初始状态下无法凑出这些金额。
2. 状态转移方程
对于每个金额 i,我们尝试使用每种硬币 coin,如果 i >= coin,则 dp[i] 可以通过 dp[i - coin] + 1 来更新。具体来说,状态转移方程为: dp[i]=min(dp[i],dp[i−coin]+1)dp[i]=min(dp[i],dp[i−coin]+1)
3. 初始化
dp[0] = 0:凑出金额 0 不需要任何硬币。- 其他
dp[i]初始化为INT_MAX或amount + 1,表示初始状态下无法凑出这些金额。
4. 结果
- 如果
dp[amount]仍然是初始的大值,说明无法凑出该金额,返回空数组。 - 否则,通过回溯
dp数组来找到具体的硬币组合。
vector<int> solution(vector<int> coins, int amount) {
vector<int> dp(amount + 1, INT_MAX);
dp[0] = 0;
// 动态规划填表
for (int i = 1; i <= amount; ++i) {
for (int coin : coins) {
if (i >= coin && dp[i - coin] != INT_MAX) {
dp[i] = min(dp[i], dp[i - coin] + 1);
}
}
}
// 如果无法凑出该金额,返回空数组
if (dp[amount] == INT_MAX) {
return {};
}
// 回溯找到具体的硬币组合
vector<int> result;
while (amount > 0) {
for (int coin : coins) {
if (amount >= coin && dp[amount] == dp[amount - coin] + 1) {
result.push_back(coin);
amount -= coin;
break;
}
}
}
// 确保结果按预期顺序输出
sort(result.begin(), result.end(), greater<int>());
return result;
}
代码解释
-
初始化:
dp数组初始化为INT_MAX,表示初始状态下无法凑出这些金额。dp[0]初始化为 0,因为凑出金额 0 不需要任何硬币。
-
动态规划填表:
- 遍历每个金额
i,对于每个硬币coin,如果i >= coin且dp[i - coin]不是INT_MAX,则更新dp[i]。
- 遍历每个金额
-
回溯找到具体的硬币组合:
- 从
amount开始,通过dp数组回溯,找到每个硬币的具体组合。
- 从
-
排序:
- 在回溯过程中,将结果存储在
result中,最后对result进行降序排序,确保大面值的硬币在前。
- 在回溯过程中,将结果存储在
-
检查结果:
- 比较返回的结果和预期结果,判断是否正确。
总结
通过动态规划方法,我们可以高效地解决硬币找零问题,找到凑出给定总金额所需的最少硬币数量及其具体组合。动态规划的核心思想是将问题分解为子问题,并通过保存子问题的解来避免重复计算,从而提高算法的效率。希望本文的详细解释和代码实现能帮助你更好地理解和应用动态规划方法。