|
| 1 | +## 每日一题 - 小飞电梯调度问题 |
| 2 | + |
| 3 | +### 信息卡片 |
| 4 | + |
| 5 | +- 时间: 2019-07-31 |
| 6 | +- 题目链接:暂无 |
| 7 | +- tag:`Math` `Dynamic Programming` |
| 8 | + |
| 9 | +### 题目描述: |
| 10 | + |
| 11 | +``` |
| 12 | +微软亚洲研究所所在的希格玛大厦一共有6部电梯。在高峰时间,每层都有人上下,电梯在每层都停。实习生小飞常常会被每层都停的电梯弄得很不耐烦,于是他提出了这样一个办法: |
| 13 | +由于楼层并不太高看没在繁忙的上下班时间,每层电梯从一层往上走时,我们只允许电梯停在其中的某一层。所有的乘客都从一楼上电梯,到达某层楼后,电梯停下来,所有乘客再从这里爬楼梯到自己的目的层。 |
| 14 | +在一楼的时候,每个乘客选择自己的目的层,电梯则自动计算出应停的楼层。 |
| 15 | +问:电梯停在哪一层楼,能够保证这次乘坐电梯的所有乘客爬楼梯的层数之和最少。 |
| 16 | +
|
| 17 | +扩展: |
| 18 | +
|
| 19 | +1.如何在O(n)的时间复杂度完成? |
| 20 | +2.往上爬楼梯,总是比往下走要累的。假设往上爬一个楼层,要耗费k单位的能量,而往下走只需要耗费1单位的能量,那么如果题目条件改为让所有人消耗的能量最少,这个问题怎么解决呢? |
| 21 | +这个问题可以用类似上面的分析方法来解答看,因此笔者不再累述,留给读者自行解决。 |
| 22 | +3.在一个高楼里面,电梯只在某一个楼层停,这个政策还是不太人性化。如果电梯会在k个楼层停呢?读者可以发挥自己的想象力,看看如何寻找最优方案。 |
| 23 | +``` |
| 24 | + |
| 25 | +### 参考答案 |
| 26 | + |
| 27 | + |
| 28 | +题意是 |
| 29 | +每层都停 => 只停一层,其余让人爬楼梯;所有人爬梯之和最小 |
| 30 | +选择目的层(i),在i层下的人数是T[i],根据大家选择的目的层计算在哪一层(X)停最优 |
| 31 | +sum(1~N){T[i]*|i-x|}的最小值 |
| 32 | + |
| 33 | +从简单易想到的方式开始; |
| 34 | +从1楼开始直到顶层,算出在每层人需要爬梯的总和数组result |
| 35 | +找出Min(result)下标 |
| 36 | +时间复杂度是O(N^2) |
| 37 | + |
| 38 | +```js |
| 39 | +/** |
| 40 | +* 两个测试数据 |
| 41 | +* nPerson = [0, 1, 3, 4, 2, 3] |
| 42 | +* nPerson = [0, 1, 0, 2, 2, 6] |
| 43 | +*/ |
| 44 | +function original(nPerson) { // nPerson首元素设0,使楼层与下标对应 |
| 45 | + // nPerson[i] 在i层下的人, N 总楼层 |
| 46 | + let result = [0]; // 存各层结果 |
| 47 | + let target = 1; // 最小值下标 |
| 48 | + for(let x = 1; x < nPerson.length; x++) { // 目标楼层x |
| 49 | + result[x]=0; |
| 50 | + for(let i = 1; i < nPerson.length; i++) { // 人在哪层停留 |
| 51 | + result[x] += nPerson[i]*Math.abs(x-i); |
| 52 | + } |
| 53 | + if(result[target] > result[x]) { |
| 54 | + target = x |
| 55 | + } |
| 56 | + } |
| 57 | + return target; |
| 58 | +} |
| 59 | +``` |
| 60 | + |
| 61 | +进一步考虑(动态规划) |
| 62 | +假设在i层停,共需要爬Y阶;在i层有N2人,在i层以下共N1人,i层以上共N3人 |
| 63 | +如果在i-1层停,相比i层变化Y+N2+N3-N1 = Y - (N1-N2-N3) => N1 > (N2 + N3)时会减少爬阶数 |
| 64 | +如果在i+1层停,相比i层变化Y-N3+N2+N1 = Y - (N3-N2-N1) => N3 > (N2 + N1)时会减少爬阶数 |
| 65 | +所以在N1 > N2+N3时应该在i-1层停,N3 > N2+N1时应该在i+1层停; 否则在i层停 |
| 66 | + |
| 67 | +初始状态电梯停在第一层,向上进行状态的变迁,开始时N2 + N1 - N3 < 0 |
| 68 | +sum越来越小,直到某一层N2 + N1 >= N3,就没有必要在往上走了。这时已求出最合适的楼层了 |
| 69 | + |
| 70 | +```js |
| 71 | +function betterOne(nPerson) { // 首元素设空, 下标就与楼层对应了,nPerson的长度-1就是楼层数 |
| 72 | + let N1 = 0; |
| 73 | + let N2 = nPerson[1]; |
| 74 | + let N3 = 0; |
| 75 | + let target = 1; |
| 76 | + // 第一层时,算出人需要走的楼梯数Y和在一楼以上的人数N3 |
| 77 | + for(let i = 2; i < nPerson.length; i++) { |
| 78 | + N3 += nPerson[i]; |
| 79 | + } |
| 80 | + // 再来优化 |
| 81 | + for(let i = 2; i < nPerson.length; i++) { |
| 82 | + if (N1+N2 < N3) { // 在i+1层停较优 |
| 83 | + target = i; |
| 84 | + N1 += N2 |
| 85 | + N3 -= nPerson[i] |
| 86 | + N2 = nPerson[i] |
| 87 | + } else { |
| 88 | + break |
| 89 | + } |
| 90 | + } |
| 91 | + return target |
| 92 | +} |
| 93 | +``` |
| 94 | + |
| 95 | +扩展问题2的解 |
| 96 | +向上爬比向下走更耗费体力,假设上楼是下楼耗费能量的k倍;k大于1 |
| 97 | +比较消耗能量的大小决定楼层,只需在动态规划方式上增加权重即可 |
| 98 | + |
| 99 | +```js |
| 100 | +function betterOnewithWeight(nPerson, k) { // 首元素设空, 下标就与楼层对应了,nPerson的长度-1就是楼层数 |
| 101 | + let N1 = 0; |
| 102 | + let N2 = nPerson[1]; |
| 103 | + let N3 = 0; |
| 104 | + let target = 1; |
| 105 | + // 第一层时,算出人需要走的楼梯数Y和在一楼以上的人数N3 |
| 106 | + for(let i = 2; i < nPerson.length; i++) { |
| 107 | + N3 += nPerson[i]; |
| 108 | + } |
| 109 | + // 再来优化 |
| 110 | + for(let i = 2; i < nPerson.length; i++) { |
| 111 | + if (N1+N2 < N3*k) { // 在i+1层停比较好 |
| 112 | + target = i; |
| 113 | + N1 += N2 |
| 114 | + N3 -= nPerson[i] |
| 115 | + N2 = nPerson[i] |
| 116 | + } else { |
| 117 | + break; |
| 118 | + } |
| 119 | + } |
| 120 | + return target |
| 121 | +} |
| 122 | +``` |
| 123 | + |
| 124 | +### 其他优秀解答 |
| 125 | +中位数方法 |
| 126 | + |
| 127 | +假设两个人在2楼和9楼下。那么在2-9楼之间任意层停,两人走楼梯的层数和是不变的 |
| 128 | +换一组(第二小、第二大)人也是这么处理 |
| 129 | +将每个人要去的楼层从低到高逐一排列,找到中位数,此中位数就是最优楼层 |
| 130 | + |
| 131 | +时间复杂度O(N) |
| 132 | + |
| 133 | +```js |
| 134 | +/** |
| 135 | +* 中位数方法 |
| 136 | +*/ |
| 137 | +function median(nPerson) { |
| 138 | + const newArr = []; // 存楼层 |
| 139 | + for(let i=0; i < nPerson.length; i++) { |
| 140 | + while(nPerson[i] > 0) { |
| 141 | + newArr.push(i) |
| 142 | + nPerson[i]-- |
| 143 | + } |
| 144 | + } |
| 145 | + let len = newArr.length; |
| 146 | + // 返回楼层中位数 |
| 147 | + return len % 2 == 1 ? newArr[(len+1)/2] : newArr[len/2] |
| 148 | +} |
| 149 | +``` |
0 commit comments