硬币面值组合
【轉】http://www.hawstein.com/posts/8.7.html
題目
原文:
Given an infinite number of quarters (25 cents), dimes (10 cents), nickels (5 cents) and pennies (1 cent), write code to calculate the number of ways of representing n cents.
譯文:
我們有25分,10分,5分和1分的硬幣無限個。寫一個函數計算組成n分的方式有幾種?
解答
一開始,我覺得使用遞歸不斷地累加四種幣值的硬幣,當累加到n分,組成方式就加1。 最后就能得到一共有多少種組合方式,聽起來挺正確的,然后寫了以下代碼:
int cnt = 0;
void sumn(int sum, int n){
if(sum >= n){
if(sum == n) ++cnt;
return;
}
else{
sumn(sum+25, n);
sumn(sum+10, n);
sumn(sum+5, n);
sumn(sum+1, n);
}
}
但,這是錯誤的。問題出在哪?有序與無序的區別!這個函數計算出來的組合是有序的, 也就是它會認為1,5和5,1是不一樣的,導致計算出的組合里有大量是重復的。 那要怎么避免這個問題?1,5和5,1雖然會被視為不一樣,但如果它們是排好序的, 比如都按從大到小排序,那么就都是5,1了,這時就不會重復累計組合數量。 可是我們總不能求出答案來后再搞個排序吧,多費勁。這時我們就可以在遞歸上做個手腳, 讓它在計算的過程中就按照從大到小的幣值來組合。比如, 現在我拿了一個25分的硬幣,那下一次可以取的幣值就是25,10,5,1;如果我拿了一個 10分的,下一次可以取的幣值就只有10,5,1了;這樣一來,就能保證,同樣的組合, 我只累計了一次,改造后的代碼如下:
int cnt = 0;
void sumN(int sum, int c, int n){
if(sum >= n){
if(sum == n) ++cnt;
return;
}
else{
if(c >= 25)
sumN(sum+25, 25, n);
if(c >= 10)
sumN(sum+10, 10, n);
if(c >= 5)
sumN(sum+5, 5, n);
if(c >= 1)
sumN(sum+1, 1, n);
}
}
有個全局變量總覺得看起來不好看,于是我們可以把這個全局變量去掉, 讓函數來返回這個組合數目,代碼如下:
int sum_n(int sum, int c, int n){
int ways = 0;
if(sum <= n){
if(sum == n) return 1;
if(c >= 25)
ways += sum_n(sum+25, 25, n);
if(c >= 10)
ways += sum_n(sum+10, 10, n);
if(c >= 5)
ways += sum_n(sum+5, 5, n);
if(c >= 1)
ways += sum_n(sum+1, 1, n);
}
return ways;
}
CTCI原文中給出的解法如下:
int make_change(int n, int denom){
int next_denom = 0;
switch(denom){
case 25:
next_denom = 10;
break;
case 10:
next_denom = 5;
break;
case 5:
next_denom = 1;
break;
case 1:
return 1;
}
int ways = 0;
for(int i=0; i*denom<=n; ++i)
ways += make_change(n-i*denom, next_denom);
return ways;
}
也是從幣值大的硬幣開始,每次取i個(i乘以幣值要小于等于n), 然后接著去取幣值比它小的硬幣,這時就是它的一個子問題了,遞歸調用。 具體來怎么來理解這個事呢?這樣,比如我要湊100分,那我先從25分開始, 我先取0個25分,然后遞歸調用去湊100分;再然后我取1個25分,然后遞歸調用去湊100-25 =75分;接著我取2個25分,遞歸調用去湊100-25*2=50分……這些取了若干個 25分然后再去遞歸調用,取的就是10分了。一直這樣操作下去,我們就會得到, 由若干個25,若干個10分,若干個5分和若干個1分組成的100分,而且, 這里面每種幣值的數量都可以為0。
完整代碼如下:
#include <iostream>
using namespace std;
int cnt = 0;
void sumN(int sum, int c, int n){
if(sum >= n){
if(sum == n) ++cnt;
return;
}
else{
if(c >= 25)
sumN(sum+25, 25, n);
if(c >= 10)
sumN(sum+10, 10, n);
if(c >= 5)
sumN(sum+5, 5, n);
if(c >= 1)
sumN(sum+1, 1, n);
}
}
int sum_n(int sum, int c, int n){
int ways = 0;
if(sum <= n){
if(sum == n) return 1;
if(c >= 25)
ways += sum_n(sum+25, 25, n);
if(c >= 10)
ways += sum_n(sum+10, 10, n);
if(c >= 5)
ways += sum_n(sum+5, 5, n);
if(c >= 1)
ways += sum_n(sum+1, 1, n);
}
return ways;
}
int make_change(int n, int denom){
int next_denom = 0;
switch(denom){
case 25:
next_denom = 10;
break;
case 10:
next_denom = 5;
break;
case 5:
next_denom = 1;
break;
case 1:
return 1;
}
int ways = 0;
for(int i=0; i*denom<=n; ++i)
ways += make_change(n-i*denom, next_denom);
return ways;
}
int main(){
int n = 10;
sumN(0, 25, n);
cout<<cnt<<endl;
cout<<make_change(n, 25)<<endl;
cout<<sum_n(0, 25, n)<<endl;
return 0;
}
總結
- 上一篇: 读《构建之法》的心得体会
- 下一篇: 【MFC】0xC0000005: 读取位