各大计算机公司 笔试及面试 题目 - 淘宝

备注: 转至 周磊的博客,http://blog.youkuaiyun.com/v_JULY_v/article/details/6313257


64、淘宝校园笔试题
goengine
N个鸡蛋放到M个篮子中,篮子不能为空,要满足:对任意不大于N的数量,能用若干个篮子中鸡蛋的和表示。

写出函数,对输入整数N和M,输出所有可能的鸡蛋的放法。

比如对于9个鸡蛋5个篮子
解至少有三组:
1 2 4 1 1
1 2 2 2 2
1 2 3 2 1

    思路一、
    Sorehead在我的微软100题,维护地址上,已经对此题有了详细的思路与阐释,以下是他的个人思路+代码:
Sorehead
思路:
    1、由于每个篮子都不能为空,可以转换成每个篮子先都有1个鸡蛋,再对剩下的N-M个鸡蛋进行分配,这样就可以先求和为N-M的所有排列可能性。
    2、假设N-M=10,求解所有排列可能性可以从一个比较简单的递规来实现,转变为下列数组:
(10,0)、(9,1)、(8,2)、(7,3)、(6,4)、(5,5)、(4,6)、(3,7)、(2,8)、(1,9)
这里对其中第一个元素进行循环递减,对第二个元素进行上述递规重复求解,
例如(5,5)转变成:(5,0)、(4,1)、(3,2)、(2,3)、(1,4)
由于是求所有排列可能性,不允许有重复记录,因此结果就只能是非递增或者非递减队列,这里我采用的非递增队列来处理。
    3、上面的递规过程中对于像(4,6)这样的不符合条件就可以跳过不输出,但递规不能直接跳出,必须继续进行下去,因为(4,6)的结果集中还是有不少能符合条件的。
我写的是非递规程序,因此(4,6)这样的组合我就直接转换成4,4,2,然后再继续做处理。
    4、N-M的所有排列可能性已经求出来了,里面的元素全部加1,如果N-M<M,剩下的元素就全部是1,这样N个鸡蛋放入M个篮子的所有可能性就全部求出来了。注意排列中可能元素数量会超过篮子数量M,去除这样的排列即可。

    5、接下来的结果就是取出上述结果集中不满足“对于任意一个不超过N的正整数,都能由某几个篮子内蛋的数量相加得到”条件的记录了。
首先是根据这个条件去除不可能有结果的情况:如果M>N,显而易见这是不可能有结果的;那对于给定的N值,M是否不能小于某个值呢,答案是肯定的。
    6、对于给定的N值,M值最小的组合应该是1,2,4,8,16,32...这样的序列,这样我们就可以计算出M的最小值可能了,如果M小于该值,也是不可能有结果的。

    7、接下来,对于给定的结果集,由于有个篮子的鸡蛋数量必须为1,可以先去掉最小值大于1的记录;同样,篮子中鸡蛋最大数量也应该不能超过某值,该值应该在N/2左右,具体值要看N是奇数还是偶数了,原因是因为超过这个值,其它篮子的鸡蛋数量全部相加都无法得到比该值小1的数。
    8、最后如何保证剩下的结果中都是符合要求的,这是个难题。当然有个简单方法就是对结果中的每个数挨个进行判断。

  1. //下面是他写的代码:  
  2. void malloc_egg(int m, int n)  
  3. {  
  4.     int *stack, top;  
  5.     int count, max, flag, i;  
  6.   
  7.     if (m < 1 || n < 1 || m > n)  
  8.         return;  
  9.   
  10.     //得到m的最小可能值,去除不可能情况  
  11.     i = n / 2;  
  12.     count = 1;  
  13.     while (i > 0)  
  14.     {  
  15.         i /= 2;  
  16.         count++;  
  17.     }  
  18.     if (m < count)  
  19.         return;  
  20.   
  21.     //对m=n或m=n-1进行特殊处理  
  22.     if (m >= n - 1)  
  23.     {  
  24.         if (m == n)  
  25.             printf("1,");  
  26.         else  
  27.             printf("2,");  
  28.         for (i = 0; i < m; i++)  
  29.             printf("1,");  
  30.         printf("/n");  
  31.         return;  
  32.     }  
  33.   
  34.     if ((stack = malloc(sizeof(int) * (n - m))) == NULL)  
  35.         return;  
  36.   
  37.     stack[0] = n - m;  
  38.     top = 0;  
  39.   
  40.     //得到篮子中鸡蛋最大数量值  
  41.     max = n % 2 ? n / 2 : n / 2 - 1;  
  42.     if (stack[0] <= max)  
  43.     {  
  44.         printf("%d,", n - m + 1);  
  45.         for (i = 1; i < m; i++)  
  46.             printf("1,");  
  47.         printf("/n");  
  48.     }  
  49.   
  50.     do  
  51.     {  
  52.         count = 0;  
  53.         for (i = top; i >= 0 && stack[i] == 1; i--)  
  54.             count++;  
  55.   
  56.         if (count > 0)  
  57.         {  
  58.             top -= count;  
  59.             stack[top]--;  
  60.             count++;  
  61.             //保证是个非递增数列  
  62.             while (stack[top] < count)  
  63.             {  
  64.                 stack[top + 1] = stack[top];  
  65.                 count -= stack[top];  
  66.                 top++;  
  67.             }  
  68.             stack[++top] = count;  
  69.         }  
  70.         else  
  71.         {  
  72.             stack[top]--;  
  73.             stack[++top] = 1;  
  74.         }  
  75.   
  76.         //去除元素数量会超过篮子数量、超过鸡蛋最大数量值的记录  
  77.         if (top >= m - 1)  
  78.             continue;  
  79.         if (stack[0] > max)  
  80.             continue;  
  81.   
  82.         //对记录中的每个数挨个进行判断,保证符合条件二  
  83.         flag = 0;  
  84.         count = m - top;  
  85.         for (i = top; i >= 0; i--)  
  86.         {  
  87.             if (stack[i] >= count)  
  88.             {  
  89.                 flag = 1;  
  90.                 break;  
  91.             }  
  92.             count += stack[i] + 1;  
  93.         }  
  94.         if (flag)  
  95.             continue;  
  96.   
  97.         //输出记录结果值  
  98.         for (i = 0; i < m; i++)  
  99.         {  
  100.             if (i <= top)  
  101.                 printf("%d,", stack[i] + 1);  
  102.             else  
  103.                 printf("1,");  
  104.         }  
  105.         printf("/n");  
  106.     }  
  107.     while (stack[0] > 1);  
  108.   
  109.     free(stack);  
  110. }  

存在的问题:
    1、程序我没有进行严格的测试,所以不能保证中间没有问题,而且不少地方都可以再优化,中间有些部分处理得不是很好,有时间我再好好改进一下。
    2、有些情况还可以特殊处理一下,例如M>N/2时,似乎满足条件一的所有组合都是满足条件二的;当N=(2的n次方-1),M=n时,结果只有一个,就是1、2、4、...(2的n-1次方),应该可以根据这个对其它结果进行推导。
    3、这种方法是先根据条件一得到所有可能性,然后在这个结果集中去除不符合条件二的,感觉效率不是很好。个人觉得应该有办法可以直接把两个条件一起考虑,靠某种方式主动推出结果,而不是我现在采用的被动筛选方式。其实我刚开始就是想采用这种方式,但得到的结果集中总是缺少一些了排列可能。

思路二、以下是晖的个人思路:
qq675927952
    N个鸡蛋分到M个篮子里(N>M),不能有空篮子,对于任意不大于于N的数,保证有几个篮子的鸡蛋数和等于此数,编程实现输入N,M两个数,输出所有鸡蛋的方法。

    1、全输出的话本质就是搜索+剪枝。
    2、(n,m,min)表示当前状态,按照篮子里蛋的数目从小到大搜索。搜到了第m个篮子,1..m个篮子面共放了n个蛋,当前的篮子放了min个蛋。下一个扩展(n+t,m+1,t),for t=min...n+1。当n+(M-m)*min>N (鸡蛋不够时)或者2^(M-m)*n+2^(M-m)-1<N(鸡蛋太多)时 把这个枝剪掉……  ; 
    3、太多时的情况如下: n,n+1,2n+2,4n+4,8n+8....。代码如下:

  1. //copyright@ 晖  
  2. //updated:  
  3. //听从网友yingmg的建议,再放到编译器上,调整下了缩进。  
  4. #include <iostream>  
  5. using namespace std;  
  6. long pow2[20];  
  7. int N,M;  
  8. int ans[1000];  
  9. void solve( int n , int m , int Min )  
  10. {  
  11.     if(n == N && m == M)  
  12.     {  
  13.         for(int i=1;i<=M;i++)  
  14.         {  
  15.             cout<<ans[i]<<" ";    
  16.         }   
  17.         cout<<endl;  
  18.         return ;    
  19.     }   
  20.     else if( n + (M-m)*Min > N || N > pow2[M-m]*n + pow2[M-m]-1)  
  21.         return ;  
  22.     else  
  23.     {  
  24.         for(int i = Min; i <= n+1; i++)  
  25.         {  
  26.             ans[m+1] =  i;      
  27.             solve(n+i,m+1,i);   
  28.         }   
  29.           
  30.     }   
  31. }  
  32.   
  33. int main()  
  34. {  
  35.     pow2[0] = 1;  
  36.     for(int i=1;i<20;i++)  
  37.     {  
  38.         pow2[i] = pow2[i-1]<<1;    
  39.     }  
  40.     cin>>N>>M;  
  41.     if( M > N || pow2[M]-1 < N)  
  42.     {  
  43.         cout<<"没有这样的组合"<<endl;     
  44.     }     
  45.     solve( 0 , 0 , 1 );  
  46.     system("pause");   
  47.     return 0;   
  48. }   
  此思路二来自: http://blog.youkuaiyun.com/qq675927952/archive/2011/03/30/6290131.aspx


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值