一个货币组合的问题

今年五月份的时候,宿舍的人找实习,笔试碰到一道货币组合的问题,是这样子的:

现在我们有1元、2元、5元、10元、20元和50元,每种面值都有若干张。请问,如果要用这些货币组成100元的话,有多少种方法?

他们笔试回来,就讲了这个问题。当时本菜就写了个暴力的方法(反正计算机有这么大的算力……)

 1 #include <cstdio>
 2 #include <cstdlib>
 3 
 4 int 
 5 main(int argc, char** argv)
 6 {
 7     int count = 0;
 8     int sum = 0;
 9     
10     for (int fifty = 0; fifty <=2 ; fifty++) {
11         for (int twenty = 0; twenty <= 5; twenty++) {
12             for (int ten =0; ten <= 10; ten++) {
13                 for (int five = 0; five <= 20; five++) {
14                     for (int two = 0; two <= 50; two++) {
15                         for (int one = 0; one <= 100; one++) {
16                             sum = 50*fifty + 20*twenty + 10*ten + 5*five + 2*two + 1*one;
17                             if (sum != 100) {
18                                 continue;
19                             }
20                             count++;
21                         }
22                     }
23                 }
24             }
25         }
26     }
27     
28     printf("total combination: %d.\n", count);
29     
30     system("pause");
31     return 0;
32 }

这种方法容易想,但是不容易扩展。如果货币的种类变更,比如现在规定不允许使用2元,然后多了100元的货币可供选择。那么代码要改的地方就多了。

同时,程序的运行速度也不快(暂不考虑)。

 

最近学了点动态规划的知识,尤其是0-1背包问题。感觉跟这个货币组合的问题有点类似,想法是这样的:

  定义函数f(i, j):要组合的货币目标为i,可供选择的货币从第0到第j种(可以把j看成大小为j的数组,在本题中v[0]=1, v[1]=2, v[2] =5……),f就是在i,j的条件下,货币组合的数目。(i > 0, j >= 0)

  所以我们可以知道,

  1. 当i = 1时,组合目标为1元,只能用1张1元来组合。那么f(i, j) = 1。
  2. 当j = 0时,那么无论组合目标为多少,只有1元钱能够用来组合。那么f(i, j) = 1。
  3. 当i < v[j]时,比i大的v[j]是不能用来组合的,所以f(i, j) = f(i, j-1)。
  4. 当i = v[j]时,有两种情况,一是用1张的v[j]来组合;二是没有用到v[j],组合方法有f(i, j-1)种,所以f(i, j) = 1 + f(i, j-1)。
  5. 当i > v[j]时,这是最普遍的情形。有两种情况,一是没用到v[j],是f(i, j-1);二是用到了一张v[j],但是仍然可以再用v[j],所以是f(i-v[j], j)。则两者相加,一共是f(i, j-1)+f(i-v[j], j)。

  或者可以这样思考第五步的第二种情况:只用了一张v[j],是f(i-v[j], j-1),用到两张v[j],是f(i-2*v[j], j-1),用到三张v[j],是f(i-3*v[j], j-1)......所以f(i, j) = f(i, j-1) + f(i-v[j], j-1) + f(i-2*v[j], j-1) + f(i-3*v[j], j-1)+......

  而f(i-v[j], j) = f(i-v[j], j-1) + f(i-2*v[j], j-1) + f(i-3*v[j], j-1) + ......代入上一段的f(i, j),可以得到f(i, j) = f(i, j-1) + f(i-v[j], j),同第五步。

 

  根据1~5的分析,我们可以写出递归的函数:

int v[6] = {1, 2, 5, 10, 20, 50}; // 零钱面额

// 递归版本
// idxChange为零钱的序号,sum为货币组合的目标金额
int MoneyCombination(int sum, int idxChange)
{
    if (sum == 1 || idxChange == 0) {
        return 1;
    }
    if (sum < v[idxChange]) {
        return MoneyCombination(sum, idxChange-1);
    }
    if (sum == v[idxChange]) {
        return 1 + MoneyCombination(sum, idxChange-1);
    }
    return MoneyCombination(sum, idxChange-1) + MoneyCombination(sum-v[idxChange], idxChange);
}

 

 同理我们可以写出非递归的函数:

 1 // 非递归版本
 2 // table也可以为[100][6],此处设为[101][6],便于理解 
 3 int v[6] = {1, 2, 5, 10, 20, 50};
 4 int table[101][6] = {0};
 5 int MoneyCombination(void)
 6 {
 7     // i为货币组合的目标金额,j为零钱的序号 
 8     int i, j;
 9     i = 0; 
10     j = 0;
11     // 初始化第二步的情况。我们忽略第一步的情况,因为它被包含在第二步和第三步的处理当中。 
12     for (i = 1; i < 101; i++) {
13         table[i][0] = 1;
14     }
15     for (i = 1; i < 101; i++) {
16         for (j = 1; j < 6; j++) {
17             if (i < v[j]) {
18                 table[i][j] = table[i][j-1];
19             }
20             if (i == v[j]) {
21                 table[i][j] = 1 + table[i][j-1];
22             }
23             if (i > v[j]) {
24                 table[i][j] = table[i][j-1] + table[i-v[j]][j];
25             }
26         }
27     }
28     return table[100][5];
29 }

 

也同理,我们可以写出打印零钱组合的函数(此函数亦可以计算零钱组合):

 1 // 打印零钱组合,原理还是动态规划的原理 
 2 int v[6] = {1, 2, 5, 10, 20, 50};
 3 string result;
 4 string str[6] = {"1", "2", "5", "10", "20", "50"};
 5 int count = 0;
 6 
 7 void print(int i, int j, string result) 
 8 {
 9     if (i == 0 && j == 0) {
10         printf("%s\n", result.c_str());
11         count++;
12     }
13     else if (i > 0 && j == 0) {
14         print(i-v[j], j, result + str[j]+ " ");
15     }
16     
17     else if (i < v[j]) {
18         print(i, j-1, result);
19     }
20     else if (i > 0 && j > 0) {
21         print(i-v[j], j, result + str[j] + " ");
22         print(i, j-1, result);
23     }
24 }

 

 

动态规划只学了一点点,正好用在了解答这道题目上。算法是很有趣的东西,值得我们去琢磨。

 


 

参考:

整数拆分问题

用1元,2元,5元,10元,20元和50元的纸币组成100元,共有多少种情况?

 

posted @ 2014-10-23 20:57  nipan  阅读(578)  评论(0编辑  收藏  举报