最优硬币组合问题题解
1. 问题描述
小C有多种不同面值的硬币,每种硬币的数量是无限的。他希望知道,如何使用最少数量的硬币,凑出给定的总金额N。小C对硬币的组合方式很感兴趣,但他更希望在满足总金额的同时,使用的硬币数量尽可能少。 例如:小C有三种硬币,面值分别为 1, 2, 5。他需要凑出总金额为 18。一种最优的方案是使用三个 5 面值的硬币,一个 2 面值的硬币和一个 1 面值的硬币,总共五个硬币。
2. 解题思路
2.1 问题分析
该问题属于经典的“硬币组合问题”变体,目的是在给定的硬币面值和目标金额的情况下,找到一种硬币组合,使得总金额达到目标值且硬币数量最少。小C拥有面值为1, 2, 5的硬币,需要凑出总金额为18。根据题意,显然这是一个“最优化”问题,其中目标是最小化所用的硬币数量。
在解决此类问题时,常用动态规划和深度优先搜索(DFS)算法。这里采用了深度优先搜索算法,通过遍历所有可能的组合,找到满足条件的最小硬币组合。在DFS中,我们递归地选择硬币面值,当总金额等于0时保存当前组合并更新最小硬币数。
2.2 算法思想与模板
该算法使用深度优先搜索(DFS)配合剪枝优化来解决问题,整体流程如下:
-
排序和降序排列:首先对硬币面值从大到小进行排序,以便优先使用面值大的硬币,从而更快凑出目标金额。优先选用大面值的硬币可以减少组合中的硬币数量,加速收敛到最优解。
-
DFS递归:递归过程中维护两个参数,
i表示当前硬币面值的索引,amount表示当前还需凑的金额。- 若
amount为0,则表示当前组合已经达成目标,此时更新最优解。 - 若
amount < 0或i超出硬币面值数组,则回溯到上一层。
- 若
-
剪枝优化:在DFS中,当发现当前组合的硬币数量已经大于当前的最优解时,直接停止递归,避免无效的进一步计算。
下面是DFS的代码模板:
void dfs(int i, int amount, vector<int> & combination, vector<int> &coins) {
if (amount == 0) {
// 更新最小硬币数及最优组合
}
if (amount < 0 || i >= coins.size()) {
return;
}
combination.push_back(coins[i]);
dfs(i, amount - coins[i], combination, coins);
combination.pop_back();
dfs(i + 1, amount, combination, coins);
}
在递归过程中,通过combination.push_back(coins[i])和combination.pop_back()分别选择或取消当前硬币面值,以探索不同的组合方式。
3. 代码
#include <iostream>
#include <vector>
#include <algorithm>
#include <climits>
using namespace std;
vector<int> ans;
vector<int> best_combination;
int min_coins = INT_MAX;
bool cmp(int a, int b) {
return a > b;
}
void dfs(int i, int amount, vector<int> &combination, vector<int> &coins) {
if (amount == 0) {
if (combination.size() < min_coins) {
min_coins = combination.size();
best_combination = combination;
}
return;
}
if (amount < 0 || i >= coins.size()) {
return;
}
combination.push_back(coins[i]);
dfs(i, amount - coins[i], combination, coins);
combination.pop_back();
dfs(i + 1, amount, combination, coins);
}
vector<int> solution(vector<int> array, int total) {
sort(array.begin(), array.end(), cmp);
vector<int> combination;
dfs(0, total, combination, array);
return best_combination;
}
int main() {
vector<int> result = solution({1, 2, 5}, 18);
for (int num : result) {
cout << num << " ";
}
cout << endl;
cout << (result == vector<int>({5, 5, 5, 2, 1})) << endl;
return 0;
}
4. 代码详解
-
变量初始化:
best_combination:用于存储当前最优解的组合。min_coins:记录当前组合的最少硬币数,初始值为INT_MAX。
-
自定义排序函数
cmp:- 定义降序排序函数
cmp,保证大面值硬币优先使用。
- 定义降序排序函数
-
dfs函数:- 递归地查找硬币组合,通过
combination.push_back(coins[i])和combination.pop_back()操作控制当前选择的硬币。 - 当
amount == 0时,组合已达成目标,更新最优解。 - 剪枝条件:如果
amount < 0或索引i超出边界,立即返回避免无效搜索。
- 递归地查找硬币组合,通过
-
主函数
solution:- 调用
sort对硬币数组排序,然后初始化组合并调用dfs搜索。
- 调用
-
测试输出:
- 使用示例
{1, 2, 5}和目标金额18,输出最优组合。
- 使用示例
5. 性能分析
该算法采用DFS实现,时间复杂度为(n为硬币种类数量)。尽管DFS效率较低,但对于小规模硬币和目标金额问题较为适用。
6. 图解分析
以下是DFS过程的图解说明,以简化的硬币集{1, 2}和目标金额4为例:
dfs(0, 4) // 初始值
/ \
dfs(0, 3) dfs(1, 4)
/ \ / \
dfs(0, 2) dfs(1, 3) dfs(1, 2) dfs(2, 4)
每次选择一种硬币并递归,直至找到最优解,或跳出递归。