牛客 BM70 兑换零钱:
题目:给定数组arr,arr中所有的值都为正整数且不重复。每个值代表一种面值的货币,每种面值的货币可以使用任意张,再给定一个aim,代表要找的钱数,求组成aim的最少货币数。
如果无解,请返回-1.
数据范围:数组大小满足 0≤n≤100000≤n≤10000 , 数组中每个数字都满足 0<val≤100000<val≤10000,0≤aim≤50000≤aim≤5000
要求:时间复杂度 O(n×aim)O(n×aim) ,空间复杂度 O(aim)O(aim)。
思路
这题的思路说实话,不在动态规划题组里,我还真想不到。也算是投个巧吧。
设f(aim)为题解,那么在拿到aim的目标时,必定会出现一个情况就是,拿到给定数额中的一个后,达到aim这个状态。假设给定数组为{a1,a2,a3,……,an}。于是f(aim) = min(f(aim – a1), f(aim – a2), ……,f(aim – an)) + 1;当然上述基于aim大于所有给定的数据an。
所以这就关联上动态规划了,将求取当前状态转换为求取子状态。所以构建数组dp[aim+1]初始状态dp[0] = 0; 意思就是当aim=0的时候,只需要取0个硬币就能完成。而dp[aim]和dp[aim-an]之间的关系就是,dp[aim] = min(dp[aim – an] + 1, dp[aim]);而an就是数组{an}中的成员,即arr中的成员,所以an = arr[n];于是上述关系可进一步转换为:
dp[aim] = min(dp[aim – arr[n]] + 1, dp[aim]);
再加上前提,aim肯定要大于arr[n], 才需要考虑该不该拿这个硬币。
代码
于是给出方案代码:
import java.util.*;
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
* 最少货币数
* @param arr int整型一维数组 the array
* @param aim int整型 the target
* @return int整型
*/
public int minMoney (int[] arr, int aim) {
// write code here
int size = arr.length;
int[] dp = new int[aim+1];
for(int i = 0; i <= aim; i++) {
//将dp数组成员都初始化为aim+1,因为最多的方案就是aim个硬币
//如果出现aim+1,即无法给出aim这个结果的硬币组合方案
dp[i] = aim + 1;
}
dp[0] = 0;
for(int i = 1; i <= aim; i ++) {
for(int j = 0; j < size; j++) {
//要保证当前硬币面额,比当前的目标数额小
if(i >= arr[j]) {
dp[i] = Math.min(dp[i], dp[i - arr[j]] + 1);
}
}
}
//当dp[aim] > aim的时候,就说明这肯定是初始化后就没赋值,即无满
//足条件的组合方式
return dp[aim] > aim ? -1 : dp[aim];
}
}
给出c++代码:
#include <vector>
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
* 最少货币数
* @param arr int整型vector the array
* @param aim int整型 the target
* @return int整型
*/
int minMoney(vector<int>& arr, int aim) {
// write code here
vector<int> res(aim+1, aim+1);
res[0] = 0;
for(int i = 1; i < aim + 1; i++) {
for(int j = 0; j < arr.size(); j++) {
if(arr[j] <= i) {
res[i] = min(1 + res[i - arr[j]], res[i]);
}
}
}
return res[aim] > aim ? -1 : res[aim];
}
};