最少硬币数问题(贪心法|回溯法|动规法)

今天我们来聊聊一个有意思的问题:给定n枚硬币,指定一个数额s,问总和为s的硬币数最少是多少枚?为了突出问题本质,假定一定存在硬币组合,使得总和为s。看似很简单对不对,不过还是有很多东西可以聊聊,下面介绍4种解法。

贪心

就像别人来找零,要你给他115块钱,那么你肯定不会给他115张一块钱的币,在币种充足情况下,你会这么核算:100+10+5。事实上,贪心算法就是这么做的。

#include <iostream>
#include <algorithm>
using namespace std;

int main() {
    int C,n;
    int coin[100];
    cin >> C >> n;
    for (int i = 1; i <= n; i++) {
        cin >> coin[i];
    }
    sort(coin+1,coin+n+1);
    int sum = 0;
    int i = n;
    int ans = 0;
    while (sum != C) {
        if (sum + coin[i] <= C) {
            ans ++;
            sum += coin[i];
        }
        i--;
    }
    cout << ans << endl;
    return 0;
}

给你25 10 10 10 3 1 1这七枚硬币凑足30元(不多不少,就要30),那么按照贪心,选择应该是25+3+1+1,这四枚硬币。但实际上,10+10+10这三枚就够了。应当说明的是,贪心算法在常见币种面额情况下还是能够得到最优答案的,但如若从整体上来说,只能说贪心法得到的是近似最优解。

回溯

对于每一枚硬币,无非就是选与不选。于是,暴力回溯的方法出来了:

#include <iostream>
#include <algorithm>
using namespace std;
int C,n;
int v[10];
int ans = 0x7fffffff;

void dfs(int i, int sum, int cnt) {
    if (i > n) {
        if (sum == C) {
            ans = min(ans,cnt);
        }
        return ;
    }
    if(sum == C) {
        ans = min(ans,cnt);
        return ;
    }
    dfs(i+1,sum,cnt);
    dfs(i+1,sum+v[i],cnt+1);
}

int main() {
    cin >> C >> n;
    for (int i = 1; i <= n; i++) {
        cin >> v[i];
    }
    dfs(1,0,0);
    cout << ans << endl;
    return 0;
}

暴力枚举的另外一种形式

选与不选之中,选记为1,不选记为0,那么这些硬币的选择状态会形成一个位向量。例如,对于上述贪心法,位向量是:1000111。这样,我们就可以枚举0000000-1111111的所有位向量,看看哪个是凑足钱数而币数又最少的方案。借助bitset:

#include <iostream>
#include <algorithm>
#include <bitset>
using namespace std;

int main() {
    int C,n;
    cin >> C >> n;
    int coin[12];
    int ans = 1 << 21;
    for (int i = 0; i < n; i++) {
        cin >> coin[i];
    }
    for (int i = 0; i < (1 << n); ++i) {
        bitset<10> bs(i);
        int sum = 0;
        for (int j = 0; j < 10; ++j) {
            sum = sum + bs[j]*coin[j];
        }
        if (sum == C) {
            ans = min(ans,(int)bs.count()); // count函数用来求bitset中1的位数
        }
    }
    cout << ans << endl;
    return 0;
}

动态规划

(这里问题性质有所改变,币面值给定,不限币数,凑足是≥的意思)
这种方法是准确而耗时又比较少的。
此处可以思考一分钟——该问题具有最优子结构。
定义dp[i][j]的意义为:在前i个物品恰好凑足j价值的最少币数。

#include <iostream>
#include <algorithm>
#include <string.h>
#include <math.h>
using namespace std;
int dp[12][50];

int main() {
    int C,n;
    cin >> C >> n;
    int coin[12];
    for (int i = 1; i <= n; i++) {
        cin >> coin[i];
    }
    for (int i = 1; i <= C; i++) {
        dp[1][i] = ceil((double)i/coin[1]);
    }
    for (int i = 2; i <= n; i++) {
        for (int j = 1; j <= C; j++) {
            if (j <= coin[i]) {
                dp[i][j] = 1;
            }
            else {
                dp[i][j] = min(dp[i-1][j],dp[i-1][j-coin[i] ]+1);
            }
        }
    }
    cout << dp[n][C] << endl;
    return 0;
}

如有错误,欢迎指正。

  • 5
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值