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+rightToLeftj且i>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。 - 如果没人在过桥,此时在桥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