给定一个target,代表钱,再给一个数组money,代表货币种类,比如有【1,2,5,10】,把target换成零钱,最少的货币数。如果不能把钱找开,则返回-1,taget = 0 ,返回0;
方法1: 第一感觉是用贪心算法做。
int minMoney(vector& money,int target) {
if (target < 0)
return -1;
sort(money.begin(),money.end());
int res = 0;
int i = money.size();
while(target && i > 0) {
int temp = target / money[i-1];
res += temp;
target = target % money[i-1];
i--;
}
if (target > 0)
return -1;
return res;
}
如果target=29,money = {2,3,4},应该是6张4元的,一张2元,一张3元,但是算法给出7张4元,所以返回了-1.所以这样是错误的,不能用贪心。
方法2:用动态规划,dp[i][j] 表示用前i个货币,组成j元的货币数,那么转移方程就是
dp[i][j] = min(dp[i-1][j],dp[i][j-money[i]]+1)
j元组成数,要么不需要第i种货币,如果需要第i个货币,则是dp[i][j-money[i]] + 1;
时间复杂度和空间复杂度都是O(money.size() * target)
int minMoney2(vector& money,int target) {
sort(money.begin(),money.end());
int n = money.size();
if (target < 0)
return -1;
vector> dp(n,vector(target+1,INT_MAX));
for (int i = 0; i < n; i++)
dp[i][0] = 0;
for (int j = 1; j <= target; j++)
if (j >= money[0] && dp[0][j-money[0]] != INT_MAX)
dp[0][j] = dp[0][j-money[0]] + 1;
for (int i = 1; i < n; i++) {
for (int j = 1; j <= target; j++) {
if (j >= money[i]) {
if (dp[i][j - money[i]] == INT_MAX)
dp[i][j] = dp[i - 1][j];
else
dp[i][j] = min(dp[i - 1][j], dp[i][j - money[i]] + 1);
}
else {
dp[i][j] = dp[i-1][j];
}
}
}
if (dp[n-1][target] == INT_MAX)
return -1;
return dp[n-1][target];
}
方法3:动态规划,但是压缩了空间。
int minMoney3(vector &money,int target) {
int n = money.size();
sort(money.begin(),money.end());
vector dp(target+1,INT_MAX);
dp[0] = 0;
for (int j = 1; j <= target; j++) {
if (j >= money[0] && dp[j-money[0]] != INT_MAX)
dp[j] = dp[j - money[0]] + 1;
}
for (int i = 1; i < n; i++) {
for (int j = 1; j <= target; j++) {
if (j >= money[i] && dp[j-money[i]] != INT_MAX)
dp[j] = min(dp[j-money[i]]+1,dp[j]);
}
}
if(dp[target] == INT_MAX)
return -1;
return dp[target];
}
和上一个题目不同的是,每一个货币不是无限次使用,比如说,money = {2,3,5,5},那么2,3只能用1次,5用2次。
dp[i][j]表示j元,只用前i张零钱,最小货币数,换不开则是INT_MAX,所以有
dp[i][j] = min(dp[i-1][j-money[i]]+1,dp[i-1][j])
方法1:二维dp,时间空间复杂度都是O(M*N),M表示money数组数,N表示target大小。
int minCoins3(vector &money,int target) {
int n = money.size();
if (target < 0)
return -1;
sort(money.begin(),money.end());
vector> dp(n,vector(target+1,INT_MAX));
for (int i = 0; i < n; i++) dp[i][0] = 0;
for (int j = 1; j <= target; j++) {
if (j == money[0])
dp[0][j] = 1;
}
for (int i = 1; i < n; i++) {
for (int j = 1; j <= target; j++) {
if (j >= money[i] && dp[i-1][j-money[i]] != INT_MAX) {
dp[i][j] = min(dp[i-1][j-money[i]]+1,dp[i-1][j]);
}
else {
dp[i][j] = dp[i-1][j];
}
}
}
if (dp[n-1][target] == INT_MAX)
return -1;
return dp[n-1][target];
}
方法2:压缩DP。绝妙的部分在于内部循环是从后向前循环的
int minCoins4(vector &money,int target) {
int n = money.size();
if (target < 0)
return -1;
sort(money.begin(),money.end());
vector dp(target+1,INT_MAX);
dp[0] = 0;
if (money[0] <= target)
dp[money[0]] = 1;
for (int i = 1; i < n; i++) {
for (int j = target; j > 0; j--) {//精彩
if (j >= money[i] && dp[j-money[i]] != INT_MAX)
dp[j] = min(dp[j],dp[j-money[i]]+1);
}
}
if (dp[target] == INT_MAX)
return -1;
return dp[target];
}
这个相当于把搜索路径都要输出。
方法1:用DFS。
void dfs(vector &money,int target,int &res,int sum,int pos) {
if (sum >= target) {
if (sum == target) res++;
return;
}
for (int i = pos; i < money.size(); i++) {
dfs(money,target,res,sum+money[i],i);
}
}