Chinaunix首页 | 论坛 | 博客
  • 博客访问: 4560668
  • 博文数量: 1214
  • 博客积分: 13195
  • 博客等级: 上将
  • 技术积分: 9105
  • 用 户 组: 普通用户
  • 注册时间: 2007-01-19 14:41
个人简介

C++,python,热爱算法和机器学习

文章分类

全部博文(1214)

文章存档

2021年(13)

2020年(49)

2019年(14)

2018年(27)

2017年(69)

2016年(100)

2015年(106)

2014年(240)

2013年(5)

2012年(193)

2011年(155)

2010年(93)

2009年(62)

2008年(51)

2007年(37)

分类: IT业界

2012-05-11 11:54:27

据说是google 2011年秋季校园面试题目:

  如果你手上有100000000块钱,而人民币的面值有100,50,20,10,5,1,求这些钱共有>多少种组合可以得到你手上的钱。

我想到的方法是把所有情况遍历一遍,每次计数器加1:


点击(此处)折叠或打开

  1. #几种面值都可以被总价整出,所以问题变得简单,一种非elegant的代码。
  2. amount = 100000000
  3. nominal = [100, 50, 20, 10, 5, 1]
  4. combination = set()

  5. for a in range(amount//nominal[0] + 1):
  6.     for b in range(amount//nominal[1] + 1):
  7.         for c in range(amount//nominal[2] + 1):
  8.             for d in range(amount//nominal[3] + 1):
  9.                 for e in range(amount//nominal[4] + 1):
  10.                     remain = amount - (100*a + 50*b + 20*c + 10*d + 5*e)
  11.                     if remain >= 0 and remain <= amount:
  12.                         combination.add( (a,b,c,d,e,remain) )

  13. print(len(combination))


for 循环可以优化,减少一定次数;顺次遍历所有情况,所以set()可以不要;python3效率不高,硬盘狂响,想必是内存不够了,换成C。

点击(此处)折叠或打开

  1. void calculate(int amount)
  2. {
  3.     int nominal[] = {100, 50, 20, 10, 5, 1};
  4.     unsigned long long combination = 0;
  5.     int a, b, c, d, e;
  6.     int remain[4] = {0};

  7.     for (a = 0; a <= amount / nominal[0]; ++a) {
  8.         remain[0] = amount - 100 * a;
  9.         for (b = 0; b <= remain[0] / nominal[1]; ++b) {
  10.             remain[1] = remain[0] - 50 * b;
  11.             for (c = 0; c <= remain[1] / nominal[2]; ++c) {
  12.                 remain[2] = remain[1] - 20 * c;
  13.                 for (d = 0; d <= remain[2] / nominal[3]; ++d) {
  14.                     remain[3] = remain[2] - 10 * d;
  15.                     for (e = 0; e <= remain[3] / nominal[4]; ++e) {
  16.                         //printf("a:%d, b:%d, c:%d, d:%d, e:%d, f:%d\n",
  17.                         // a, b, c, d, e, remain[3]-5*e);
  18.                         combination++;
  19.                     }
  20.                 }
  21.             }
  22.         }
  23.     }

  24.     printf("\nCombination: %llu\n", combination);
  25.     return ;
  26. }

把for循环中每次计算的数字先计算了,降低计算量:

点击(此处)折叠或打开

  1. unsigned long long calculate2(int amount)
  2. {
  3.     int nominal[] = {100, 50, 20, 10, 5, 1};
  4.     unsigned long long combination = 0;
  5.     int a, b, c, d, e;
  6.     int remain[4] = {0};
  7.     int times[4] = {0};
  8.     int n;

  9.     n = amount / nominal[0] + 1;
  10.     for (a = 0; a < n; ++a) {
  11.         remain[0] = amount - 100 * a;
  12.         times[0] = remain[0] / nominal[1] + 1;
  13.         for (b = 0; b < times[0]; ++b) {
  14.             remain[1] = remain[0] - 50 * b;
  15.             times[1] = remain[1] / nominal[2] + 1;
  16.             for (c = 0; c < times[1]; ++c) {
  17.                 remain[2] = remain[1] - 20 * c;
  18.                 times[2] = remain[2] / nominal[3] + 1;
  19.                 for (d = 0; d < times[2]; ++d) {
  20.                     remain[3] = remain[2] - 10 * d;
  21.                     times[3] = remain[3] / nominal[4] + 1;
  22.                     for (e = 0; e < times[3]; ++e) {
  23.                         //printf("a:%d, b:%d, c:%d, d:%d, e:%d, f:%d\n",
  24.                         // a, b, c, d, e, remain[3]-5*e);
  25.                         combination++;
  26.                     }
  27.                 }
  28.             }
  29.         }
  30.     }

  31.     printf("\nCombination: %llu\n", combination);
  32.     return combination;
  33. }


无论5元的出多少张,剩下的1元都可以来填补,所以最后一个循环可以略去:

点击(此处)折叠或打开

  1. void calculate3(int amount)
  2. {
  3.     int nominal[] = {100, 50, 20, 10, 5, 1};
  4.     unsigned long long combination = 0;
  5.     int a, b, c, d;
  6.     int remain[4] = {0};
  7.     int times[4] = {0};
  8.     int n;

  9.     n = amount / nominal[0] + 1;
  10.     for (a = 0; a < n; ++a) {
  11.         remain[0] = amount - 100 * a;
  12.         times[0] = remain[0] / nominal[1] + 1;
  13.         for (b = 0; b < times[0]; ++b) {
  14.             remain[1] = remain[0] - 50 * b;
  15.             times[1] = remain[1] / nominal[2] + 1;
  16.             for (c = 0; c < times[1]; ++c) {
  17.                 remain[2] = remain[1] - 20 * c;
  18.                 times[2] = remain[2] / nominal[3] + 1;
  19.                 for (d = 0; d < times[2]; ++d) {
  20.                     remain[3] = remain[2] - 10 * d;
  21.                     times[3] = remain[3] / nominal[4] + 1;
  22.                     combination += times[3];
  23.                 }
  24.             }
  25.         }
  26.     }

  27.     printf("\nCombination: %llu\n", combination);
  28.     return ;
  29. }


d变量的for循环还可以有一小步优化:

点击(此处)折叠或打开

  1. unsigned long long calculate5(int amount)
  2. {
  3.     int nominal[] = {100, 50, 20, 10, 5, 1};
  4.     /* 估计这个64位的数不够容纳结果 */
  5.     unsigned long long combination = 0;
  6.     int a, b, c, d;
  7.     int remain[4] = {0};
  8.     int times[4] = {0};
  9.     int n;

  10.     n = amount / nominal[0] + 1;
  11.     remain[0] = amount + 100; // +100 for the first time minus
  12.     for (a = 0; a < n; ++a) {
  13.         remain[0] -= 100;
  14.         times[0] = remain[0] / nominal[1] + 1;
  15.         remain[1] = remain[0] + 50;
  16.         for (b = 0; b < times[0]; ++b) {
  17.             remain[1] -= 50;
  18.             times[1] = remain[1] / nominal[2] + 1;
  19.             remain[2] = remain[1] + 20;
  20.             for (c = 0; c < times[1]; ++c) {
  21.                 remain[2] -= 20;
  22.                 times[2] = remain[2] / nominal[3] + 1;
  23.                 for (d = 0; d < times[2]; ++d)
  24.                     /* division 得到的是商,余数舍去,所以这个for不能用等差数列替换 */
  25.                     combination += (remain[2] - 10*d) / nominal[4];
  26.                 combination += times[2];
  27.             }
  28.         }
  29.     }

  30.     printf("\nCombination: %llu\n", combination);
  31.     return combination;
  32. }


不能再优化了,我还写了一个check函数,检查变换的正确性:

点击(此处)折叠或打开

  1. int check()
  2. {
  3.     unsigned int i;
  4.     int num[3] = {20, 107, 200};

  5.     for (i = 0; i < sizeof(num) / sizeof(int); ++i)
  6.     {
  7.         unsigned long long cmp = calculate2(num[i]);
  8.         unsigned long long ret = calculate5(num[i]);
  9.         if (ret != cmp )
  10.         {
  11.             printf("Problem emerge: %llu, %llu at amount{%d}\n", ret, cmp, num[i]);
  12.             return -1;
  13.         }
  14.     }
  15.     return 0;
  16. }


问题:

  目前,这个函数只可以计算小数据量,amount=10000时结果是  -->  174716753951,如果amount是10万,我的电脑跑12个小时都没出结果,并且可以知道unsigned long long 肯定是越界了好多次了,这时最好在加上计算越界多少次的功能,为了统计正确的结果。但实际上这么做已经没有意义了,因为需要考虑用一个更好的算法。

  实话实说,上面的代码都是垃圾,我也先入为主,暂时想不到更好的方法,有谁想到更好的方法了吗?

阅读(1284) | 评论(4) | 转发(0) |
0

上一篇:向量化与并行计算

下一篇:高楼扔鸡蛋

给主人留下些什么吧!~~

laoliulaoliu2012-06-14 13:32:26

_Rayx: 复杂度是O(N*T),N是钱,T是币种,现在T比较小,看成线性也行。.....
但这并不是一个线性问题。
钱越多,组合并不是线性增长的

_Rayx2012-06-14 08:10:46

laoliulaoliu: 请问您是这个意思吗?
我用列举法计算发现f(10000) = 174716753951,
然后 f(n) = 174716753951 + f(n - 10000) 计算出来f(n) ?
我不确定这个是否是一个线性的问.....
复杂度是O(N*T),N是钱,T是币种,现在T比较小,看成线性也行。

laoliulaoliu2012-06-13 22:59:37

_Rayx: 这个不是一个简单的动态规划么?
dp表示i块钱的组合方法,然后用币种从小到大累加即可。.....
请问您是这个意思吗?
我用列举法计算发现f(10000) = 174716753951,
然后 f(n) = 174716753951 + f(n - 10000) 计算出来f(n) ?
我不确定这个是否是一个线性的问题,或者没有理解你的意思?

_Rayx2012-06-13 20:23:16

这个不是一个简单的动态规划么?
dp表示i块钱的组合方法,然后用币种从小到大累加即可。