## 每日一题 - 小飞电梯调度问题

### 信息卡片

- 时间： 2019-07-31
- 题目链接：暂无
- tag：`Math` `Dynamic Programming`

### 题目描述：

```
微软亚洲研究所所在的希格玛大厦一共有6部电梯。在高峰时间，每层都有人上下，电梯在每层都停。实习生小飞常常会被每层都停的电梯弄得很不耐烦，于是他提出了这样一个办法：
由于楼层并不太高看没在繁忙的上下班时间，每层电梯从一层往上走时，我们只允许电梯停在其中的某一层。所有的乘客都从一楼上电梯，到达某层楼后，电梯停下来，所有乘客再从这里爬楼梯到自己的目的层。
在一楼的时候，每个乘客选择自己的目的层，电梯则自动计算出应停的楼层。
问：电梯停在哪一层楼，能够保证这次乘坐电梯的所有乘客爬楼梯的层数之和最少。

扩展：

1.如何在O(n)的时间复杂度完成？
2.往上爬楼梯，总是比往下走要累的。假设往上爬一个楼层，要耗费k单位的能量，而往下走只需要耗费1单位的能量，那么如果题目条件改为让所有人消耗的能量最少，这个问题怎么解决呢？
这个问题可以用类似上面的分析方法来解答看，因此笔者不再累述，留给读者自行解决。
3.在一个高楼里面，电梯只在某一个楼层停，这个政策还是不太人性化。如果电梯会在k个楼层停呢？读者可以发挥自己的想象力，看看如何寻找最优方案。
```

### 参考答案


题意是
每层都停 => 只停一层，其余让人爬楼梯；所有人爬梯之和最小 
选择目的层(i)，在i层下的人数是T[i]，根据大家选择的目的层计算在哪一层(X)停最优 
sum(1～N){T[i]*|i-x|}的最小值 

从简单易想到的方式开始； 
从1楼开始直到顶层，算出在每层人需要爬梯的总和数组result 
找出Min(result)下标 
时间复杂度是O(N^2) 

```js
/**
* 两个测试数据
* nPerson = [0, 1, 3, 4, 2, 3]
* nPerson = [0, 1, 0, 2, 2, 6]
*/
function original(nPerson) { // nPerson首元素设0，使楼层与下标对应
  // nPerson[i] 在i层下的人， N 总楼层
  let result = [0]; // 存各层结果
  let target = 1; // 最小值下标
  for(let x = 1; x < nPerson.length; x++) { // 目标楼层x
    result[x]=0;
    for(let i = 1; i < nPerson.length; i++) { // 人在哪层停留
      result[x] += nPerson[i]*Math.abs(x-i);
    }
    if(result[target] > result[x]) {
      target = x
    }
  }
  return target;
}
```

进一步考虑(动态规划)
假设在i层停，共需要爬Y阶；在i层有N2人，在i层以下共N1人，i层以上共N3人   
如果在i-1层停，相比i层变化Y+N2+N3-N1 = Y - (N1-N2-N3) => N1 > (N2 + N3)时会减少爬阶数  
如果在i+1层停，相比i层变化Y-N3+N2+N1 = Y - (N3-N2-N1) => N3 > (N2 + N1)时会减少爬阶数  
所以在N1 > N2+N3时应该在i-1层停，N3 > N2+N1时应该在i+1层停; 否则在i层停 

初始状态电梯停在第一层，向上进行状态的变迁，开始时N2 + N1 - N3 < 0  
sum越来越小，直到某一层N2 + N1 >= N3，就没有必要在往上走了。这时已求出最合适的楼层了  

```js
function betterOne(nPerson) { // 首元素设空, 下标就与楼层对应了，nPerson的长度-1就是楼层数
    let N1 = 0;
    let N2 = nPerson[1];
    let N3 = 0;
    let target = 1;
    // 第一层时，算出人需要走的楼梯数Y和在一楼以上的人数N3
    for(let i = 2; i < nPerson.length; i++) {
        N3 += nPerson[i];
    }
    // 再来优化
    for(let i = 2; i < nPerson.length; i++) {
        if (N1+N2 < N3) { // 在i+1层停较优
            target = i;
            N1 += N2
            N3 -= nPerson[i]
            N2 = nPerson[i]
        } else {
            break
        }
    }
    return target
}
```

扩展问题2的解
向上爬比向下走更耗费体力，假设上楼是下楼耗费能量的k倍；k大于1  
比较消耗能量的大小决定楼层，只需在动态规划方式上增加权重即可

```js
function betterOnewithWeight(nPerson, k) { // 首元素设空, 下标就与楼层对应了，nPerson的长度-1就是楼层数
    let N1 = 0;
    let N2 = nPerson[1];
    let N3 = 0;
    let target = 1;
    // 第一层时，算出人需要走的楼梯数Y和在一楼以上的人数N3
    for(let i = 2; i < nPerson.length; i++) {
        N3 += nPerson[i];
    }
    // 再来优化
    for(let i = 2; i < nPerson.length; i++) {
        if (N1+N2 < N3*k) { // 在i+1层停比较好
            target = i;
            N1 += N2
            N3 -= nPerson[i]
            N2 = nPerson[i]
        } else {
            break;
        }
    }
    return target
}
```

### 其他优秀解答
中位数方法

假设两个人在2楼和9楼下。那么在2-9楼之间任意层停，两人走楼梯的层数和是不变的  
换一组(第二小、第二大)人也是这么处理  
将每个人要去的楼层从低到高逐一排列，找到中位数，此中位数就是最优楼层  

时间复杂度O(N)

```js
/**
* 中位数方法
*/
function median(nPerson) {
    const newArr = []; // 存楼层
    for(let i=0; i < nPerson.length; i++) {
      while(nPerson[i] > 0) {
        newArr.push(i)
        nPerson[i]--
      }
    }
    let len = newArr.length;
    // 返回楼层中位数
    return len % 2 == 1 ? newArr[(len+1)/2] : newArr[len/2]
}
```
