备注: 转至 周磊的博客,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、最后如何保证剩下的结果中都是符合要求的,这是个难题。当然有个简单方法就是对结果中的每个数挨个进行判断。
- //下面是他写的代码:
- void malloc_egg(int m, int n)
- {
- int *stack, top;
- int count, max, flag, i;
- if (m < 1 || n < 1 || m > n)
- return;
- //得到m的最小可能值,去除不可能情况
- i = n / 2;
- count = 1;
- while (i > 0)
- {
- i /= 2;
- count++;
- }
- if (m < count)
- return;
- //对m=n或m=n-1进行特殊处理
- if (m >= n - 1)
- {
- if (m == n)
- printf("1,");
- else
- printf("2,");
- for (i = 0; i < m; i++)
- printf("1,");
- printf("/n");
- return;
- }
- if ((stack = malloc(sizeof(int) * (n - m))) == NULL)
- return;
- stack[0] = n - m;
- top = 0;
- //得到篮子中鸡蛋最大数量值
- max = n % 2 ? n / 2 : n / 2 - 1;
- if (stack[0] <= max)
- {
- printf("%d,", n - m + 1);
- for (i = 1; i < m; i++)
- printf("1,");
- printf("/n");
- }
- do
- {
- count = 0;
- for (i = top; i >= 0 && stack[i] == 1; i--)
- count++;
- if (count > 0)
- {
- top -= count;
- stack[top]--;
- count++;
- //保证是个非递增数列
- while (stack[top] < count)
- {
- stack[top + 1] = stack[top];
- count -= stack[top];
- top++;
- }
- stack[++top] = count;
- }
- else
- {
- stack[top]--;
- stack[++top] = 1;
- }
- //去除元素数量会超过篮子数量、超过鸡蛋最大数量值的记录
- if (top >= m - 1)
- continue;
- if (stack[0] > max)
- continue;
- //对记录中的每个数挨个进行判断,保证符合条件二
- flag = 0;
- count = m - top;
- for (i = top; i >= 0; i--)
- {
- if (stack[i] >= count)
- {
- flag = 1;
- break;
- }
- count += stack[i] + 1;
- }
- if (flag)
- continue;
- //输出记录结果值
- for (i = 0; i < m; i++)
- {
- if (i <= top)
- printf("%d,", stack[i] + 1);
- else
- printf("1,");
- }
- printf("/n");
- }
- while (stack[0] > 1);
- free(stack);
- }
存在的问题:
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....。代码如下:
- //copyright@ 晖
- //updated:
- //听从网友yingmg的建议,再放到编译器上,调整下了缩进。
- #include <iostream>
- using namespace std;
- long pow2[20];
- int N,M;
- int ans[1000];
- void solve( int n , int m , int Min )
- {
- if(n == N && m == M)
- {
- for(int i=1;i<=M;i++)
- {
- cout<<ans[i]<<" ";
- }
- cout<<endl;
- return ;
- }
- else if( n + (M-m)*Min > N || N > pow2[M-m]*n + pow2[M-m]-1)
- return ;
- else
- {
- for(int i = Min; i <= n+1; i++)
- {
- ans[m+1] = i;
- solve(n+i,m+1,i);
- }
- }
- }
- int main()
- {
- pow2[0] = 1;
- for(int i=1;i<20;i++)
- {
- pow2[i] = pow2[i-1]<<1;
- }
- cin>>N>>M;
- if( M > N || pow2[M]-1 < N)
- {
- cout<<"没有这样的组合"<<endl;
- }
- solve( 0 , 0 , 1 );
- system("pause");
- return 0;
- }