【算法】Time to Cross a Bridge 过桥的时间

Time to Cross a Bridge 过桥的时间

问题描述:

共有 k 位工人计划将 n 个箱子从旧仓库移动到新仓库。给你两个整数 n 和 k,以及一个二维整数数组 time ,数组的大小为 k x 4 ,其中 t i m e [ i ] = [ l e f t T o R i g h t i , p i c k O l d i , r i g h t T o L e f t i , p u t N e w i ] time[i] = [leftToRight_i, pickOld_i, rightToLeft_i, putNew_i] time[i]=[leftToRighti,pickOldi,rightToLefti,putNewi]

一条河将两座仓库分隔,只能通过一座桥通行。旧仓库位于河的右岸,新仓库在河的左岸。开始时,所有 k 位工人都在桥的左侧等待。为了移动这些箱子,第 i 位工人(下标从 0 开始)可以:

从左岸(新仓库)跨过桥到右岸(旧仓库),用时 l e f t T o R i g h t i leftToRight_i leftToRighti 分钟。
从旧仓库选择一个箱子,并返回到桥边,用时 p i c k O l d i pickOld_i pickOldi 分钟。不同工人可以同时搬起所选的箱子。
从右岸(旧仓库)跨过桥到左岸(新仓库),用时 r i g h t T o L e f t i rightToLeft_i rightToLefti 分钟。
将箱子放入新仓库,并返回到桥边,用时 p u t N e w i putNew_i putNewi 分钟。不同工人可以同时放下所选的箱子。
如果满足下面任一条件,则认为工人 i 的 效率低于 工人 j :
l e f t T o R i g h t i + r i g h t T o L e f t i > l e f t T o R i g h t j + r i g h t T o L e f t j l e f t T o R i g h t i + r i g h t T o L e f t i = = l e f t T o R i g h t j + r i g h t T o L e f t j 且 i > j leftToRight_i + rightToLeft_i > leftToRight_j + rightToLeft_j\\ leftToRight_i + rightToLeft_i == leftToRight_j + rightToLeft_j 且 i > j leftToRighti+rightToLefti>leftToRightj+rightToLeftjleftToRighti+rightToLefti==leftToRightj+rightToLeftji>j
工人通过桥时需要遵循以下规则:

如果工人 x 到达桥边时,工人 y 正在过桥,那么工人 x 需要在桥边等待。
如果没有正在过桥的工人,那么在桥右边等待的工人可以先过桥。如果同时有多个工人在右边等待,那么 效率最低 的工人会先过桥。
如果没有正在过桥的工人,且桥右边也没有在等待的工人,同时旧仓库还剩下至少一个箱子需要搬运,此时在桥左边的工人可以过桥。如果同时有多个工人在左边等待,那么 效率最低 的工人会先过桥。
所有 n 个盒子都需要放入新仓库,请你返回最后一个搬运箱子的工人 到达河左岸 的时间。

1 < = n , k < = 1 0 4 t i m e . l e n g t h = = k t i m e [ i ] . l e n g t h = = 4 1 < = l e f t T o R i g h t i , p i c k O l d i , r i g h t T o L e f t i , p u t N e w i < = 1000 1 <= n, k <= 10^4\\ time.length == k\\ time[i].length == 4\\ 1 <= leftToRight_i, pickOld_i, rightToLeft_i, putNew_i <= 1000 1<=n,k<=104time.length==ktime[i].length==41<=leftToRighti,pickOldi,rightToLefti,putNewi<=1000

分析

对问题的理解很关键,这个过程就是搬东西,而给出的数组是把一个过程拆成4个环节的时间消耗。

NewWareHouse-------- Bridge ----------- OldWarehouse

从只有1个工人,记为W1,需要从OldWarehouse搬box。
出发点在Left,

  • 第一步,从left_bank出发,过桥到达了OldWareHouse,这个环节的时间是leftToRighti。
  • 第二步,在OldWareHouse搬一个box,然后到桥右侧,这个环节的时间是pickOldi
  • 第三步,从right_bank出发,过桥到达了NewWareHouse,这个环节的时间是rightToLefti。
  • 第四步,在NewWareHouse放下一个box,然后到桥左侧,这个环节的时间是putNewi 。

如果工人数量为1,那么整个流程就是W1,按照4个步骤,不断的重复n次,而且最终的时间消耗,是以W1,完成第n次循环的第4步,停止计算。

但是如果工人的数量>1,就发生了变化,因为在过桥的环节,第1步和第3步,会有限制。

  1. 如果有人在过桥,那么其他人就必须等
    就是说,桥是独占的,容量是1
  2. 如果没人在过桥,此时在桥2侧等待的工人,才可以竞争这个过桥的名额。
    这个竞争其实是以一定顺序,有序的过桥,而不是抢占式的。
  • 情况1 bridge is free, 右侧优先,左侧等,即当右侧等待队列空了,左侧的工人才可以过。
  • 情况2 两侧必须以 效率最低的工人先过桥。
  • 情况3 右侧还有箱子没搬,左侧的人才能过桥。

the lowest efficiency

这里的效率efficiency是以第1,3步时间开销为维度。耗时高的表示效率低,所以做个排序,同时在效率相同的情况下,工人的编号也会影响。

到此问题基本就是要模拟整个搬箱子的流程。
很明显在过桥的环节,需要2个优先队列leftWait,rightWait,因为这个环节涉及到了效率比较。

当从左侧过桥,达到OldWareHouse,就是从leftWait出队一个[效率最低]的,进入OldWareHouse,而在WareHouse中,是没有效率影响的,工人W在T1时刻进入,那么就会在 T 2 = T 1 + p i c k O l d T2=T1+pickOld T2=T1+pickOld时刻出去,进入到rightWait。
也就是说在WareHouse环节,进入的时间是由过桥环节完成的时间点决定的,而其出去的时间是由工人自身的时间开销决定的。
所以在WareHouse,也需要一个优先队列,以工人【完工的时间 finishTime】做维度,谁的完工时间小,谁就先出去。

如果wait队列不空的情况下,出队的前提必然是bridge is free,所以需要一个标识来表示这个状态,可以使用curTime来表示桥在这个时间之后是可以使用的。
而每次wait出队,即过桥的环节,curTime都会增加。

以过桥环节来说,一开始是单侧出队,这样可以用过桥耗时累加,但是如果是2侧都有过桥的工人,那么此时的 修改时间的权利就交给了右侧,这里存在一个问题,如果T=1000时bridge free,但是此时出队的工人从WareHouse出来已经是1100,那么这个时候时间就要以最晚的来计算。

因为在过桥环节,有可能此刻从WareHouse出来然后进入wait队列,同时又第一个过桥,所以应该先把warehouse中 f i n i s h t i m e > = c u r T i m e finishtime>=curTime finishtime>=curTime的工人都分别入wait队列,确保这个时刻符合条件的工人,都已经进入队列参与排队。

然后再按照先右后左的顺序,以及效率最低优先过桥的要求,完成1个过桥操作,同时刷新curtime。

一旦curtime更新了,那么此时就可能会有工人从WareHouse出,然后进入wait。

桥2侧无人等待

还有一个情况上面已经提到,就是当2侧wait没人,需要从WareHouse中选一个出队,要求这个人的出队时间是最小的。

对于模拟题来说,细节就是魔鬼,要分析清楚,然后选择合适的结构

代码

class Solution {
    public int findCrossingTime(int n, int k, int[][] time) {
        Arrays.sort(time, (a, b) -> a[0] + a[2] - b[0] - b[2]); // 稳定排序
        
        PriorityQueue<int[]> workL = new PriorityQueue<int[]>((a, b) -> a[1] - b[1]);
        PriorityQueue<int[]> workR = new PriorityQueue<int[]>((a, b) -> a[1] - b[1]); 
        PriorityQueue<int[]> waitL = new PriorityQueue<int[]>((a, b) -> b[0] - a[0]); // 下标越大效率越低
        PriorityQueue<int[]> waitR = new PriorityQueue<int[]>((a, b) -> b[0] - a[0]);
        for (int i = k - 1; i >= 0; i--){
            waitL.add(new int[]{i, 0});
        }  
        int cur = 0;
        while (n > 0) {
            while (!workL.isEmpty() 
            && workL.peek()[1] <= cur){
                waitL.add(workL.poll()); // 左边完成放箱
            } 
            while (!workR.isEmpty() 
            && workR.peek()[1] <= cur){
                waitR.add(workR.poll()); // 右边完成搬箱
            } 
            if (!waitR.isEmpty()) { 
                // 右边过桥
                var p = waitR.poll();
                cur += time[p[0]][2];
                p[1] = cur + time[p[0]][3];
                workL.add(p); // 放箱
            } else if (!waitL.isEmpty()) { 
                // 左边过桥
                var p = waitL.poll();
                cur += time[p[0]][0];
                p[1] = cur + time[p[0]][1];
                workR.add(p); // 搬箱
                n--;
            }
            // cur 过小,找个最小的放箱/搬箱完成时间来更新 cur 
            else if (workL.isEmpty()){
                cur = workR.peek()[1];
            }  
            else if (workR.isEmpty()){
                cur = workL.peek()[1];
            } 
            else{
                cur = Math.min(workL.peek()[1], workR.peek()[1]);
            } 
        }
        while (!workR.isEmpty()) {
            var p = workR.poll(); // 右边完成搬箱
            // 如果没有排队,直接过桥;否则由于无论谁先过桥,最终完成时间都一样,所以也可以直接计算
            cur = Math.max(p[1], cur) + time[p[0]][2];
        }
        return cur; // 最后一个过桥的时间
    }
} 

时间复杂度 O ( N log ⁡ k ) O(N\log k) O(Nlogk)

空间复杂度 O ( k ) O(k) O(k)

​ 代码来源灵神大佬,略修改。

Tag

Array
Simulation
Heap

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Eric.Cui

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值