题目链接
前置知识
01背包
参考我的另一篇题解01背包
完全背包
完全背包与01背包只有一个区别:01背包每个物品只能取一次,而完全背包的物品无次数限制
虽然看起来变化不大,但代码逻辑有不少出入
OK, 我们从二者代码来分析区别(务必掌握01背包)
:表示容量, :表示每个物品的重量 :表示每个物品的价值 :表示物品的数量
01背包简单实现
vector<int>f(n+1); //等价于其他语言长度为n+1的数组
for(int i = 0; i < m; i++) { //遍历物品 w.size()或v.size()也可以表示物品的数量
for(int j = n; j >= w[i]; j--) {
f[i] = max(f[i], f[i - w[i]] + v[i]);
}
}
可能会出现两个疑问:(应该在01背包的题解里说的 我忘了)
- 为什么容量要从大到小遍历 ? 不能从小到大吗?
举个例子来讲为什么不能
n = 5 w = [1,2] v = [1,2]
假设容量从 遍历到 n (小于不会变)
时
n = 1 可以放入
n = 2 可以放入 会发现被物品被多次放入了,原本的f[1]已经存放了 被更新了,我们要的应该是旧值f[1] = 0
其实这就是完全背包的方程! 物品可以取无数次 就从小容量到大容量
- 能不能先遍历容量,再遍历物品?
可以,但容量必须从大到小遍历,不然也会出现重复使用的问题,可以手推上面的例子试试
如果你真的理解了01背包,那么完全背包一眼秒,下面是完全背包的简单实现
vector<int>f(n+1); //等价于其他语言长度为n+1的数组
for(int i = 0; i < m; i++) { //遍历物品 w.size()或v.size()也可以表示物品的数量
for(int j = w[i]; j <= n; j++) { //比如物品为2 容量为4 此时他会使用2次
f[i] = max(f[i], f[i - w[i]] + v[i]);
}
}
OK, 回归本题
解题思路
-
简化题意:
- 求恰好凑出数额的硬币的最小值,我们把抽象成, 每一个物品的价值
- 并且给出对应的硬币
-
求最少的硬币数很简单,我们只需要把max变成min, v[i] 变成1即可(初始化f时要设置一个大值)
-
如何求对应的硬币?
- 思考一下,最优解的情况可能由得到,这个值又可能是得到,其实是有可能形成一条链路的,这句话多半看不懂(
我也不知道在写啥) 举个例子
- 思考一下,最优解的情况可能由得到,这个值又可能是得到,其实是有可能形成一条链路的,这句话多半看不懂(
来记录选择最少的硬币数量 表示在数额时的最少硬币数,
初始化为INF,f[0]=0(不需要硬币)
拿一个来记录数额时选择的最优硬币,最开始都没选,那么
遍历 时,
此时有更少的硬币数,那么当前的硬币是要选择的, 我们把赋值为当前的
遍历 时,
此时有更少的硬币数,那么当前的硬币是要选择的, 我们把赋值为当前的
本轮结束后
第二轮
数额要从2开始 (1小于2, 不能更新)
此时明显更少,更新,更新了,说明在时选择的硬币变了,更新
本轮结束
最后一轮
我们从cnt[amount]开始取值,不断的就是上一步的最优选择
流程有点乱,我没表达清楚,后面有时间重写这个步骤
简单来说,只要,那么肯定是选择了当前这个硬币,那么在时你选择的硬币就是, 如果把目前这个硬币丢了 就是你上一次选择的最优硬币
通过代码来手推一下可能会更清晰
代码
vector<int> solution(vector<int> coins, int amount) {
int n = coins.size();
vector<int>f(amount+1,INT_MAX-1);
f[0] = 0;
vector<int>cnt(amount+1);
for(int i=0; i < n; i++) {
for(int j = coins[i]; j <= amount; j++) {
int v = f[j-coins[i]];
if(v + 1 < f[j]) {
f[j] = v + 1;
cnt[j] = coins[i];
}
}
}
vector<int>ans;
while(cnt[amount] != 0) {
ans.push_back(cnt[amount]);
amount = amount - cnt[amount];
}
if(f[n] == INT_MAX)return {};
return ans;
}