【字节跳动21届提前批】面试手撕代码——电梯调度算法
题目描述:
有一个特殊的电梯,只能从1楼载人,然后运输至某一楼(记为第N层)。现有若干乘客,每个乘客有一个自己想去的楼层,以数组表示:下标为乘客的id,数组中的内容为乘客想去的楼层。乘客在第N层下电梯后,需要爬楼梯到自己所去的楼层。现已知数组nums,求楼层N,使得乘客在N楼下电梯后,所有人所爬楼梯的总和最小。若某两个N都可以使得所爬楼层和最小,输出最小的那个楼层数N。
输入样例:
[1,2,3,3,2,5]
输出样例:
2
方法1(暴力解法):
首先,显而易见的是,所求解的N一定是在所有乘客想去的楼层中的最小值(min_floor)与最大值之间(max_floor)。那么我们遍历所有从min_floor到max_floor之间的楼层,针对每个楼层,计算这个楼层到每个乘客所想去的楼层之和。在遍历过程中,记下最小值和其所对应的楼层即可。思路很简单,详细代码如下(点击执行即可看到程序执行结果):
方法2(动态规划):
在暴力解法过程中,对于第2层和第3层的遍历过程如上图所示。上面的2、下面的3与中间nums数组的连线上的彩色的数字分别是2或者3与nums中不同数字的差的绝对值。在当前楼层遍历完成后(暴力解法代码中的floor层循环),变量acc的值为图中上面或下面彩色数字的和。对比遍历2层和3层时,针对nums[i]的楼层差(即上下颜色相同的数字),可以发现规律:当遍历第三层时,若nums[i]<3时,楼层差(下面的彩色数字)比N=2时的nums[i]大1,若nums[i]>=3时,楼层差比N=2时的nums[i]小1。用通俗语言讲,就是当N=3时,与N=2对比,那么所有想去的楼层低于3楼的乘客,都要多走一层,所有想去3楼及以上的乘客,都少走一层。根据这个规律,可以写出第N楼的所有人需要爬楼梯的总和与N-1楼时的关系式:
floor[i] += (1 if nums[j]<i else -1)
此时,动态规划的递推关系是找到了,但是还有另外一个问题,如果按照上式进行计算的话,在每个floor循环中,需要遍历nums数组统计小于i和大于等于i的人数。这样一来,两层for循环,时间复杂度其实并没有降低。所以还需要优化一下里层的统计人数的for循环。这里将nums数组做个变换,目前的nums数组的下标表示乘客的id,nums数组的内容表示每个人想去的楼层,现改为floors数组,数组的下标表示楼层,数组的内容表示此楼层有多少个人想去。举个例子:
nums = [1, 2, 3, 3, 2, 5] => floors = [0, 1, 2, 2, 0, 1]
再开辟一个数组memo,来存储想去小于某个楼层的人数总和,算法如下:
memo = [0] * len(floors) for i in range(2, len(memo)): memo[i] = memo[i-1] + floors[i-1]
最后,得到的整体的算法如下,同样的,点击执行按钮可以看到程序执行结果,也可以手动添加测试用例来验证算法的正确性: