换钱的最少货币数

换钱的最少货币数

题目描述

给定数组arr,arr中所有的值都为正整数且不重复。每个值代表一种面值的货币,每种面值的货币可以使用任意张,再给定一个 aim,代表要找的钱数,求组成aim的最少货币数。

输入描述:

输入包括两行,第一行两个整数n(0<=n<=1000)代表数组长度和aim(0<=aim<=5000),第二行n个不重复的正整数,代表 a r r ( 1 ≤ a r r i ≤ 1 0 9 ) ( 1 ≤ a r r i ≤ 1 0 9 ) arr\left( 1 \leq arr_i \leq 10^9 \right)(1≤arr_i≤10^9) arr(1arri109)(1arri109)

输出描述:

输出一个整数,表示组成 aim 的最小货币数,无解时输出-1.

示例1
输入
3 20
5 2 3
输出
4
说明
20=5*4
示例2
输入
3 0
5 2 3
输出
0
示例3
输入
2 2
3 5
输出
-1
备注:

时间复杂度 O ( n ∗ a i m ) O(n * aim) O(naim) ,空间复杂度 O ( n ) O(n) O(n)


题解:

基础解法

我们用 F[i, j] 表示状态,其含义是:在可以任意使用 a[0…i] 货币的情况下,组成 j 所需的最小张数。

F[i, j] 值来自以下两种情况:

  • 不使用 a[i] 货币,F[i, j] = F[i-1, j];
  • 使用 k 张 a[i] 货币,则 F[i, j] =F[i-1, j - k*a[i]] + k(k>=1)。

下面来推导一个这个状态转移方程:

F[i, j] = min{F[i-1, j - k*a[i]] + k(k>=0)}

​ = min{F[i-1, j], min{F[i-1, j - x*a[i]] + x(x>=1)}}

​ = min{F[i-1, j], min{F[i-1, j - a[i] - y*a[i]] + y + 1(y>=0)}}

因为:F[i, j-a[i]] = min{F[i-1, j - a[i] - y*a[i]] + y(y>=0)}

所以:F[i, j] = min{F[i-1, j], F[i, j-a[i]] + 1}

有没有发现这个跟 完全背包 递推公式非常像,只不过逻辑相反,一个最大,一个最小,所以,基础很重要2333。

基础解法代码:
#include <cstdio>
#include <cstring>
#include <algorithm>
 
using namespace std;
 
const int N = 1000;
const int M = 5001;
const int INF = 0x3f3f3f3f;
 
int n, aim;
int a[N];
int f[N][M];
 
int main(void) {
    scanf("%d%d", &n, &aim);
    for (int i = 0; i < n; ++i) scanf("%d", a + i);
    for (int i = 0; i < n; ++i) f[i][0] = 0;
    for (int j = 1; j <= aim; ++j) {
       f[0][j] = INF;
       if (j >= a[0] && f[0][j - a[0]] != INF)
           f[0][j] = f[0][j - a[0]] + 1;
    }
    for (int i = 1; i < n; ++i) {
        for (int j = 1; j <= aim; ++j) {
            f[i][j] = f[i - 1][j];
            if (j >= a[i]) f[i][j] = min(f[i][j], f[i][j - a[i]] + 1);
        }
    }
     
    printf("%d\n", f[n - 1][aim] == INF ? -1 : f[n - 1][aim]);
    return 0;
}

进阶解法:

观察上面的最终状态转移方程:F[i, j] 只跟 F[i-1, j] 以及 F[i, j-a[i]] 有关,那我们可以压缩一下空间,只用一维数组滚动进行状态转移记录。(注意:使用滚动数组时,正序遍历还是逆序遍历是个值得思考的问题)

进阶解法代码:
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int M = 5001;
const int INF = 0x3f3f3f3f;

int n, aim, val;
int f[M];

int main(void) {
    scanf("%d%d", &n, &aim);
    memset(f, INF, sizeof f);
    f[0] = 0;
    while (n--) {
        scanf("%d", &val);
        for (int i = val; i <= aim; ++i)
            f[i] = min(f[i], f[i - val] + 1);
    }
    
    printf("%d\n", f[aim] == INF ? -1 : f[aim]);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值