一个货币组合的问题

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

现在我们有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元,共有多少种情况?

时间: 2024-10-29 19:12:06

一个货币组合的问题的相关文章

一个货币组合的问题(二)——母函数解法

10月23号的时候,写了用递归和动态规划的方式解决货币组合的方法(见<一个货币组合的问题>) 这两天看到<组合数学>中,使用母函数(生成函数)解决排列组合问题的方法,觉得可以用在货币组合问题上.试验了一下,果然可以. (这里空出,详细地写一下母函数的方法,加深自己的理解.) 代码如下: 1 #include <stdio.h> 2 #include <stdlib.h> 3 4 #define MAXITEMS 128 5 6 int v[6] = {1,

C++写一个排列组合小程序

今天突然想到一个问题,有时候,针对同一个事件有多种反映,特别是游戏AI当中,这种情况下需要采取最适合的方案,哪种方案最适合,可以将每种方案的结果或影响都计算一遍,从而选择最合适的.最基本就是一个排列组合方法,将各种方案都组合出来.于是写了一个基本的N个数排列组合小程序! 开发工具:Visual Studio 2012 CTestPermutation::~CTestPermutation() { cout<<">>>>>>>>>&

VueJS实现一个货币结算自定义控件

Vue.component('currency-input', { template: ' <div> <label v-if="label">{{ label }}</label> $ <input ref="input" v-bind:value="value" v-on:input="updateValue($event.target.value)" v-on:focus=&qu

Android自定义控件之自定义组合控件(三)

前言: 前两篇介绍了自定义控件的基础原理Android自定义控件之基本原理(一).自定义属性Android自定义控件之自定义属性(二).今天重点介绍一下如何通过自定义组合控件来提高布局的复用,降低开发成本,以及维护成本. 使用自定义组合控件的好处? 我们在项目开发中经常会遇见很多相似或者相同的布局,比如APP的标题栏,我们从三种方式实现标题栏来对比自定义组件带来的好处,毕竟好的东西还是以提高开发效率,降低开发成本为导向的. 1.)第一种方式:直接在每个xml布局中写相同的标题栏布局代码 <?xm

重构之路 组合查询之传参+存储过程

上篇博文给大家一起讨论了实现组合查询的一种方法,即在U层将select语句的where子句部分组装好,赋给一个字符串变量,传到D层然后与select子句组成完整的sql语句,之后执行,返回查询结果,就是这么简单,但是博文的结尾也留下了一个疑问,这种方法的安全性有点欠佳,有没有相对好一点的办法呢? 答案是肯定的,这次我们一起来看看我实现的另一种方法.首先给大家简单介绍一下这种方法的思路,其实也比较简单,最初我是想在程序代码里写sql查询语句的,然后将组合查询的各个条件的值当做实体参数(现在实体层定

排列与组合的一些定理

一,加法原理与乘法原理 加法原理与乘法原理是排列与组合的基础.加法原理本质上是分类,乘法原理本质上是分步. 分类,就是把一个集合(某事物)分成互不相交的若干独立的部分.比如,概率论中的全概率公式就将事件分成”全划分“ 分类思想可以简化程序的时间复杂度.比如:最短路径算法-Dijkstra算法的应用之单词转换(词梯问题) 分步,就是第一步干嘛,第二步再干嘛……比如A地到D地,第一步:先到达B地:第二步,再到达C地 二,排列 P(n,r)表示从n个数中选择r个数的一个全排列 公式:P(n,r)=P(

创建我们第一个Monad

上一篇中介绍了如何使用amplified type, 如IEnumerable<T>,如果我们能找到组合amplified type函数的方法,就会更容易写出强大的程序. 我们已经说了很多次函数组合, 听起来又干又硬.函数组合其实就是简单编程,当我们写像下面这样的代码时: var customer=customerData.GetById(customerId); var order=customer.CreateOrder(); 我就是在使用函数组合,在这个例子里组合了GetById和Cre

解析器组合子

本文引自:http://www.ibm.com/developerworks/cn/java/j-lo-compose/ Ward Cunningham 曾经说过,干净的代码清晰地表达了代码编写者所想要表达的东西,而优美的代码则更进一步,优美的代码看起来就像是专门为了要解决的问题而存在的.在本文中,我们将展示一个组合式解析器的设计.实现过程,最终的代码是优美的,极具扩展性,就像是为了解析特定的语法而存在的.我们还会选取 H.248 协议中的一个例子,用上述的组合式解析器实现其语法解析器.读者在这

排列组合知识

1.在不全相异的n个物体中,其中有n1个物体是相同的,n2个物体是相同的,……nk个物体是相同的.全部物体的种类数为k,则这n个物体的全排列数为 n!/(n1!*n2!*……*nk!) 2.用n-1条边将n个顶点连接的图有n^(n-2)个. 3.圆周排列 从N个元素中取出R个元素形成圆周排列,排列数为A(N,R)/R; 同理,N个元素的圆周排列数为(N-1)!; 4.按字典序生成下一个排列组合 ①从后往前找第一个正序的尾下标i,pi>p(i-1),pi>p(i+1): ②找p(i-1)后面其大