由仓库运输问题(阿里巴巴笔试题)说开来
来源:互联网 发布:港股股票为啥50万 知乎 编辑:程序博客网 时间:2024/05/21 12:40
参加阿里巴巴的笔试,碰到一个仓库运输的问题,题目如下:
有N个仓库,每个仓库存储量已知,并且 N 个仓库排成环状(i 和 i + 1 相连,n 和 1 相连)。每次只能把货物在相邻的仓库之间运输,问最少运输多少货物使得所有仓库存储量相同。
题目很有意思,通过学习http://www.cnblogs.com/legendmaner/archive/2013/05/06/3062323.html 和 http://www.cnblogs.com/legendmaner/archive/2013/05/07/3062222.html 这两篇博客,进一步了解到了均分纸牌和糖果传递两个问题,讲解详细生动,深入浅出。
均分纸牌
[问题描述]
有 N 堆纸牌,编号分别为 1,2,…, N。每堆上有若干张,但纸牌总数必为 N 的倍数。可以在任一堆上取若于张纸牌,然后移动。
移牌规则为:在编号为 1 堆上取的纸牌,只能移到编号为 2 的堆上;在编号为 N 的堆上取的纸牌,只能移到编号为 N-1 的堆上;其他堆上取的纸牌,可以移到相邻左边或右边的堆上。
现在要求找出一种移动方法,用最少的移动次数使每堆上纸牌数都一样多。
例如 N=4,4 堆纸牌数分别为:
① 9 ② 8 ③ 17 ④ 6
移动3次可达到目的:
从 ③ 取 4 张牌放到 ④ (9 8 13 10) -> 从 ③ 取 3 张牌放到 ②(9 11 10 10)-> 从 ② 取 1 张牌放到①(10 10 10 10)。
[输 入]:
键盘输入文件名。文件格式:
N(N 堆纸牌,1 <= N <= 100)
A1 A2 … An (N 堆纸牌,每堆纸牌初始数,l<= Ai <=10000)
[输 出]:
输出至屏幕。格式为:
所有堆均达到相等时的最少移动次数。‘
[输入输出样例]
a.in:
4
9 8 17 6
屏慕显示:
3
分析:
开始容易想到在所有的牌中找到最多的一堆,然后向小的牌堆上移动,一直到所有的牌都相等,但关键是不知道往哪个方向上移动才能达到移动次数最少。
设a[i]为第i堆纸牌的张数(0<=i<=n),ave为均分后每堆纸牌的张数,ans为最小移到次数。
我们按照由左而右的顺序移动纸牌。若第i堆纸牌的张数a[i]超出平均值,则移动一次(ans+1),将超出部分留给下一堆,既第i+1堆纸牌的张数增加a[i]-ave;若第i堆纸牌的张数a[i]少于平均值,则移动一次(ans+1),由下一堆补充不足部分,既第i+1堆纸牌的张数减少ave-a[i];
问题是,在从第i+1堆中取出纸牌补充第i堆的过程中,可能会出现第i+1堆的纸牌数小于零(a[i+1]-(ave-a[i])<0 )的情况,但由于纸牌的总数是n的倍数,因此后面的堆会补充第i+1堆ave-a[i]-a[i+1]+ ave张纸牌,使其达到均分的要求。 我们在移动过程中,只是改变了移动的顺序,而移动的次数不变,因此此题使用该方法是可行的。
例如:1 2 27
我们从第二堆移出9张到第一堆后,第一堆有10张纸牌,第二堆剩下-7张纸牌,再从第三堆移动17张到第二堆,刚好三堆纸牌数都是10,最后结果是对的,从第二堆移出的牌都可以从第三堆得到。
(再思考:1 10 19,最小的代价显然就是将最后多出9张牌放到第一堆里,但是你只能移动相邻的牌,所以这9张牌先移动到第二堆里,再移动到第一堆里。反过来想,第一堆缺9张牌问相邻的借,如果第二堆有多那就直接借给第一堆好了,这样移动的次数也会最少,如果第二堆也缺的则它本身还要问下一堆所要牌,而且还要将第一堆索要的牌也继续传递下去,当然这样可以先假设其有足够的牌给第一堆,这时将出现负数,但这不是问题,它只相当一个传话的过程(如上一题-7的意思就是第二堆共缺17(10-(-7)=17)张牌,因为本身却8张,而上一堆它还缺9张,所以一共要17张),第二堆刚好合适,可以将第一堆的话传给它相邻的;)
此题的原理是贪心,从左到右让每堆牌向平均数靠拢。但负数的牌也可以移动,才是此题的关键。
int func34(int arr[], int n) { assert(arr && n>0); int i,cnt/*总共移动牌数*/,tm/*移动次数*/; long avg=0; int *a; if (n<2) { return 0; } a = new int[n]; for (i=0; i<n; i++) { a[i] = arr[i]; avg += arr[i]; } avg /= n; cnt = 0; tm =0; for (i=0; i<n-1; i++) { if (a[i] - avg != 0 ) { tm ++; } cnt += abs(a[i] - avg); a[i+1] = a[i+1] + a[i] - avg; } delete[] a; return tm; }经过以上铺垫,我们来看看糖果传递问题:
糖果传递问题(和仓库运输问题一样的,这里给出糖果传递的题目):
老师准备了一堆糖果,恰好n个小朋友可以分到数目一样多的糖果.老师要n个小朋友去拿糖果,然后围着圆桌坐好,第1个小朋友的左边是第n个小朋友其他第i个小朋友左边是第i-1个小朋友。大家坐好后,老师发现,有些小朋友抢了很多的糖果,有的小朋友只得到了一点点糖果,甚至一颗也没有,设第ai个小朋友有ai颗糖果.小朋友们可以选择将一些糖果给他左边的或者右边的小朋友,通过”糖果传递”最后使得每个小朋友得到的糖果数是一样多的,假设一颗糖果从一个小朋友传给另一个小朋友的代价是1,问怎样传递使得所耗的总代最小。
一、由均分纸牌引发的思考;
这题和均分纸牌的不同之在于是形成了环。如果没有环,我们显然可以使用贪心算法算出其最小的代价,时间复杂度为O(n);将环拆开形成一列,这样就可以在求得一个“最优”,拆开的方式有n种。选出最优的,即是解了。
why?因为在一列中的贪心算法是这样问题定义的,将向下一个相邻的节点索取这样,而且从第一个到最后一个是可以完成我们对问题的求解。所以这里将问题转换为均分纸牌问题,时间复杂度为O(n*n);
参考均分纸牌代码,稍微修改可得一下代码,未经过优化工作,利于理解:
int func35(int arr[], int n) { assert(arr && n>0); int i,cnt,sta; long avg=0; int *a; int less=INT_MAX; if (n<2) { return 0; } a = new int[n]; for (i=0; i<n; i++) { //a[i] = arr[i]; avg += arr[i]; } avg /= n; for (sta=0; sta<n; sta++) //起始位 { for (i=0; i<n; i++) //相当于移位 { a[i] = arr[(sta+i)%n]; } cnt = 0; for (i=0; i<n-1; i++) { cnt += abs(a[i] - avg); a[i+1] = a[i+1] + a[i] - avg; } if (less > cnt) { less = cnt; } } delete[] a; return cnt; }
可是,时间复杂度过高,要是ACM绝对是超时的。
更细的看问题,先将问题看成一列,即将1和n之间拆开环,求解跟均分纸牌是一样的:
A: 1 2 4 5 1 2 4 5
B: -2 -1 1 2 -2 -1 1 2
C: -2 -3 -2 0 -2 -3 -2 0
A为原数组,原有的糖果数;B为减去AVG平均数的值;C为前i项B的和,即c[i]=b1+...+bi;
则以a[1]开头(1与n之间拆开)的最优为∑abs(c[i]),这个没问题;
现在考虑从a[2]开头:
A: 1 2 4 5 1 2 4 5
B: -2 -1 1 2 -2 -1 1 2
C1: 0 -1 0 2 0 -1 0 2
显然,B数组没有改变,而C1变了。其中C1可以这么算全部减去b[1](考虑C是如何算的)
将其一般化:假设起点为k,那么以此起点开始操作的费用为:∑abs(c[i]-c[k-1]) (相当于求和的从第I到第J的和=(从1到j的和)-(从1到i的和)),是不是感觉漏了点什么呢?不是有环么?旋转了么?环形的思想已经在其中了,怎么解释?有木有发现C(C1中最后一个元素是c[1])最后的一个元素一定是0,这个是一个关键点。这样你从k开始,那么c[k-1]-c[k-1]=0前一个就成为了新的最后一个;自行证明,这里实在太巧了。。。
如果从a[3]开头:C2: 1 0 1 3 1 0 1 3 (c2[i] = c[i]-c[3])
这样就是说通过计算:∑abs(c[i]-c[k-1])这个就可以计算出以某处断裂的最优,在其全部的最优,即是所求了;
观察发现:即是找c中的某个数c[k]使得∑abs(c[i]-c[k-1])最小,中位数可以使其达到最优解,这个就不证明了。华丽转身,问题转换成求中位数了。
int func36(int a[], int n) { assert(a && n>0); int i,cnt; long avg=0; int *c; if (n<2) { return 0; } for (i=0; i<n; i++) { avg += a[i]; } avg /= n; c = new int[n]; for (i=0 ; i<n; i++) //求b数组 { c[i] = a[i] - avg; } for (i=1 ; i<n; i++) //求c数组 { c[i] = c[i-1] + c[i]; } sort(c, c+n); //排序以便求中位数 cnt = 0; // for (i=0; i<n; i++) { cnt += abs(c[i]-c[n/2]); } delete[] c; return cnt; }当然在求中位数的时候,还可以稍微提速。如利用基数排序 O(n) ,所以这个问题就在 O(n)的时间内解决了。
- 由仓库运输问题(阿里巴巴笔试题)说开来
- 2013阿里巴巴实习笔试题 最后两题 明星问题+仓库运货
- 糖果传递问题和仓库运输问题
- 由《小道消息 • 思维偏误》说开来。
- 阿里笔试题 仓库均衡问题
- 08阿里巴巴笔试题
- 阿里巴巴一道笔试题
- 阿里巴巴笔试题1.1
- 阿里巴巴笔试题1.2
- 阿里巴巴笔试题1.3
- 阿里巴巴笔试题1.4
- 阿里巴巴笔试题1.5
- 阿里巴巴笔试题
- 阿里巴巴笔试题
- 阿里巴巴笔试题
- 阿里巴巴DBA笔试题
- 阿里巴巴笔试题
- 阿里巴巴笔试题
- POJ 1026 Cipher
- NodeJs-- 新建项目实例
- 利用淘汰PC,构建低成本网络视频监控系统,只需150元成本
- ACM Steps_Chapter Eight_Section3
- Fragment间的通信
- 由仓库运输问题(阿里巴巴笔试题)说开来
- Android多媒体学习六:访问网络上的Audio对应的M3U文件,实现网络音频流的播放
- 一元一次方程类
- android反编译
- 基于bacnet-stack-0.8.0工程的Linux下通过无线网卡发送bacnet协议报文
- 网站速度测试
- apt
- 看看机器学习都有哪些比较成熟的应用
- ogre3d 移植到ios平台