【题目描述】
给定数组arr,arr中所有的值都为正整数且不重复。每个值代表一种面值的货币,每种面值的货币可以使用任意张,再给定一个aim,代表要找的钱数,求组成aim的最少货币数。
【输入描述】
输入包括两行,第一行两个整数n(0<=n<=1000)代表数组长度和aim(0<=aim<=5000),第二行n个不重复的正整数,代表数组arr(其任何一个元素取值介于1到1000000000之间 )。
【输出描述】
输出一个整数,表示组成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*aim)O(n∗aim),空间复杂度O(n)O(n)。
【代码实现 - CPP版】
#include<iostream>
#include<vector>
using namespace std;
/*
* 当前货币面值为vec[index], 待找零的金额为money
* 1. 返回值 -1:基于货币面值vec[0], ..., vec[n-1]无法找零剩余金额money
* 2. 返回值非-1:基于货币面值vec[0], ..., vec[n-1]找零金额money所需要的最少货币数量
*/
int min_coins(const vector<int>& vec, int index, int money) {
int vec_size = vec.size();
if (0 == vec_size || 0 > money) {
return -1;
}
/*
* 已经没有面值需要被考虑 -- base case
* 1. money值为0,代表此时剩余的待找零金额为0,那么所需货币数量也为0,返回0
* 2. money值非0,代表剩余的待找零金额非零,那么找不到一个合理的组合满足需求,返回-1
*/
if (index == vec_size) {
return money == 0 ? 0 : -1;
}
// 返回值初始化为-1,代表暂时还未找到有效解
int result = -1;
// 依次尝试不同数量k的面值为vec[index]的货币,其总和不能大于待找零金额money
for (int k=0; k*vec[index]<=money; k++) {
// 剩下的待找零金额money-k*vec[index]交给剩下的面值vec[index+1]去搞定
int next = min_coins(vec, index+1, money-k*vec[index]);
// 只有当后续找零组合存在,才更新最终的返回值
if (-1 != next) {
result = result == -1 ? next+k : min(result, next+k);
}
}
return result;
}
/*
* 动态规划优化
* 1. 确定可变参数:index, money -- 返回值确定
* 2. 利用可变参数组成一张二位表,该表一定可以包含所有的返回值。
* 二维表中index取值范围:0, ..., n
* 二维表中money取值范围:0, ..., aim
* 最终的二维表是一个n+1行aim+1列的表,记为dp[][]
* 3. 确定最终状态:min_cons(vec, 0, aim),也就是dp[0][aim],位于二维表0行最后一列
* 4. 确定base case(index == vec.size()),初始化二维表,
* 也即初始化二维表的最后一行dp[n][...],且dp[n][0]==1
* 5. 递推base case之外的位置的值 min_coins(vec, i, rest),
* 其中i != n && rest != 0,也即二维数组元素dp[i][rest]
* dp[i][rest] = min{dp[i+1][rest], dp[i+1][rest-vec[i]]+1, ..., dp[i+1][rest-k*vec[i]+k]}
* 也即其依赖于i+1行的所有值,需要从中寻找最小的那个值。同样的,处于同行的元素dp[i][rest-vec[j]]也依赖于
* 其左下方及正下方的值。
* 通过上面分析,二位表中所有非base case的元素都与其左侧及正下方的元素相关,确切的说:
* dp[i][rest] = min{dp[i][rest-vec[j]] + 1, dp[i+1][rest]}
* 6. 依据前述,我们已经得知二维表最后一行的元素值,现在我们只需要针对第n-1行到0行,依照从左到右的原则依次填充即可
* 最终返回dp[0][aim]即可
*/
int min_coins_dp(const vector<int> vec, int aim) {
int size = vec.size();
if (0 == size || 0 > aim) {
return -1;
}
// define the dp array
vector<vector<int>> vec_dp(size+1, vector<int>(aim+1, -1));
// init array according to the base case
vec_dp[size][0] = 0;
// scan each row from n-1 to 0 and each column from left to right
for (int i=size-1; i>=0; i--) {
for (int rest=0; rest<=aim; rest++) {
// init dp[i][j] as invalid
vec_dp[i][rest] = -1;
// judge the element below
if (-1 != vec_dp[i+1][rest]) {
// firstly, update its value to its below
vec_dp[i][rest] = vec_dp[i+1][rest];
}
// judge the left element and compare it with the below element once it exists
if (0 <= rest-vec[i] && -1 != vec_dp[i][rest-vec[i]]) {
if (-1 == vec_dp[i][rest]) {
vec_dp[i][rest] = vec_dp[i][rest-vec[i]] + 1;
} else {
vec_dp[i][rest] = min(vec_dp[i][rest], vec_dp[i][rest-vec[i]]+1);
}
}
}
}
return vec_dp[0][aim];
}
int main() {
// input the first line
int n, aim;
cin >> n >> aim;
// input the second line
vector<int> vec_coins;
int tmp = 0;
while (n --) {
cin >> tmp;
vec_coins.push_back(tmp);
}
// find the least coins number in range [0, ..., vec_coins.size()-1]
//cout << min_coins(vec_coins, 0, aim) <<endl;
cout << min_coins_dp(vec_coins, aim);
return 0;
}