Problem: Time to Cross a Bridge

There are `k` workers who want to move `n` boxes from an old warehouse to a
new one. You are given the two integers `n` and `k`, and a 2D integer array
`time` of size `k x 4` where `time[i] = [leftToRighti, pickOldi, rightToLefti,
putNewi]`.

The warehouses are separated by a river and connected by a bridge. The old
warehouse is on the right bank of the river, and the new warehouse is on the
left bank of the river. Initially, all `k` workers are waiting on the left
side of the bridge. To move the boxes, the `ith` worker (0-indexed) can :

* Cross the bridge from the left bank (new warehouse) to the right bank (old warehouse) in `leftToRighti` minutes.
* Pick a box from the old warehouse and return to the bridge in `pickOldi` minutes. Different workers can pick up their boxes simultaneously.
* Cross the bridge from the right bank (old warehouse) to the left bank (new warehouse) in `rightToLefti` minutes.
* Put the box in the new warehouse and return to the bridge in `putNewi` minutes. Different workers can put their boxes simultaneously.

A worker `i` is less efficient than a worker `j` if either condition is met:

* `leftToRighti + rightToLefti > leftToRightj + rightToLeftj`
* `leftToRighti + rightToLefti == leftToRightj + rightToLeftj` and `i > j`

The following rules regulate the movement of the workers through the bridge :

* If a worker `x` reaches the bridge while another worker `y` is crossing the bridge, `x` waits at their side of the bridge.
* If the bridge is free, the worker waiting on the right side of the bridge gets to cross the bridge. If more than one worker is waiting on the right side, the one with the lowest efficiency crosses first.
* If the bridge is free and no worker is waiting on the right side, and at least one box remains at the old warehouse, the worker on the left side of the river gets to cross the bridge. If more than one worker is waiting on the left side, the one with the lowest efficiency crosses first.

Return the instance of time at which the last worker reaches the left bank of
the river after all n boxes have been put in the new warehouse.


Python solution:
import math
import itertools
import bisect
import collections
import string
import heapq
import functools
import sortedcontainers
from typing import List, Dict, Tuple, Iterator

class Solution:
  def findCrossingTime(self, n: int, k: int, time: List[List[int]]) -> int:
    ans = 0
    leftBridgeQueue = [(-leftToRight - rightToLeft, -i) for i, (leftToRight, pickOld, rightToLeft, pickNew) in enumerate(time)]
    rightBridgeQueue = []
    leftWorkers = []
    rightWorkers = []

    heapq.heapify(leftBridgeQueue)

    while n > 0 or rightBridgeQueue or rightWorkers:
      while leftWorkers and leftWorkers[0][0] <= ans:
        i = heapq.heappop(leftWorkers)[1]
        heapq.heappush(leftBridgeQueue, (-time[i][0] - time[i][2], -i))
      while rightWorkers and rightWorkers[0][0] <= ans:
        i = heapq.heappop(rightWorkers)[1]
        heapq.heappush(rightBridgeQueue, (-time[i][0] - time[i][2], -i))
      if rightBridgeQueue:
        i = -heapq.heappop(rightBridgeQueue)[1]
        ans += time[i][2]
        heapq.heappush(leftWorkers, (ans + time[i][3], i))
      elif leftBridgeQueue and n > 0:
        i = -heapq.heappop(leftBridgeQueue)[1]
        ans += time[i][0]
        heapq.heappush(rightWorkers, (ans + time[i][1], i))
        n -= 1
      else:
        if leftWorkers and n > 0:
          ans1=leftWorkers[0][0]
        else:
          ans1=math.inf
        if rightWorkers:
          ans2=rightWorkers[0][0]
        else:
          ans2=math.inf
        ans=min(ans1,ans2)

    return ans

Java solution:
class Solution {
  public int findCrossingTime(int n, int k, int[][] time) {
    int ans = 0;
    // (leftToRight + rightToLeft, i)
    Queue<Pair<Integer, Integer>> leftBridgeQueue = createMaxHeap();
    Queue<Pair<Integer, Integer>> rightBridgeQueue = createMaxHeap();
    // (time to be idle, i)
    Queue<Pair<Integer, Integer>> leftWorkers = createMinHeap();
    Queue<Pair<Integer, Integer>> rightWorkers = createMinHeap();

    for (int i = 0; i < k; ++i)
      leftBridgeQueue.offer(new Pair<>(
          /*leftToRight*/ time[i][0] + /*rightToLeft*/ time[i][2], i));

    while (n > 0 || !rightBridgeQueue.isEmpty() || !rightWorkers.isEmpty()) {
      // Idle left workers get on the left bridge.
      while (!leftWorkers.isEmpty() && leftWorkers.peek().getKey() <= ans) {
        final int i = leftWorkers.poll().getValue();
        leftBridgeQueue.offer(new Pair<>(
            /*leftToRight*/ time[i][0] + /*rightToLeft*/ time[i][2], i));
      }
      // Idle right workers get on the right bridge.
      while (!rightWorkers.isEmpty() && rightWorkers.peek().getKey() <= ans) {
        final int i = rightWorkers.poll().getValue();
        rightBridgeQueue.offer(new Pair<>(
            /*leftToRight*/ time[i][0] + /*rightToLeft*/ time[i][2], i));
      }

      if (!rightBridgeQueue.isEmpty()) {
        // If the bridge is free, the worker waiting on the right side of the
        // bridge gets to cross the bridge. If more than one worker is waiting
        // on the right side, the one with the lowest efficiency crosses first.
        final int i = rightBridgeQueue.poll().getValue();
        ans += /*rightToLeft*/ time[i][2];
        leftWorkers.offer(new Pair<>(ans + /*putNew*/ time[i][3], i));
      } else if (!leftBridgeQueue.isEmpty() && n > 0) {
        // If the bridge is free and no worker is waiting on the right side, and
        // at least one box remains at the old warehouse, the worker on the left
        // side of the river gets to cross the bridge. If more than one worker
        // is waiting on the left side, the one with the lowest efficiency
        // crosses first.
        final int i = leftBridgeQueue.poll().getValue();
        ans += /*leftToRight*/ time[i][0];
        rightWorkers.offer(new Pair<>(ans + /*pickOld*/ time[i][1], i));
        --n;
      } else {
        // Advance the time of the last crossing worker.
        ans = Math.min(!leftWorkers.isEmpty() && n > 0 ? leftWorkers.peek().getKey()
                                                       : Integer.MAX_VALUE,
                       !rightWorkers.isEmpty() ? rightWorkers.peek().getKey() : Integer.MAX_VALUE);
      }
    }

    return ans;
  }

  private Queue<Pair<Integer, Integer>> createMaxHeap() {
    return new PriorityQueue<>(
        Comparator.comparing(Pair<Integer, Integer>::getKey, Comparator.reverseOrder())
            .thenComparing(Pair<Integer, Integer>::getValue, Comparator.reverseOrder()));
  }

  private Queue<Pair<Integer, Integer>> createMinHeap() {
    return new PriorityQueue<>(Comparator.comparingInt(Pair<Integer, Integer>::getKey)
                                   .thenComparingInt(Pair<Integer, Integer>::getValue));
  }
}


C++ solution:
class Solution {
 public:
  int findCrossingTime(int n, int k, vector<vector<int>>& time) {
    int ans = 0;
    using P = pair<int, int>;
    // (leftToRight + rightToLeft, i)
    priority_queue<P> leftBridgeQueue;
    priority_queue<P> rightBridgeQueue;
    // (time to be idle, i)
    priority_queue<P, vector<P>, greater<>> leftWorkers;
    priority_queue<P, vector<P>, greater<>> rightWorkers;

    for (int i = 0; i < k; ++i)
      leftBridgeQueue.emplace(
          /*leftToRight*/ time[i][0] + /*rightToLeft*/ time[i][2], i);

    while (n > 0 || !rightBridgeQueue.empty() || !rightWorkers.empty()) {
      // Idle left workers get on the left bridge.
      while (!leftWorkers.empty() && leftWorkers.top().first <= ans) {
        const int i = leftWorkers.top().second;
        leftWorkers.pop();
        leftBridgeQueue.emplace(
            /*leftToRight*/ time[i][0] + /*rightToLeft*/ time[i][2], i);
      }
      // Idle right workers get on the right bridge.
      while (!rightWorkers.empty() && rightWorkers.top().first <= ans) {
        const int i = rightWorkers.top().second;
        rightWorkers.pop();
        rightBridgeQueue.emplace(
            /*leftToRight*/ time[i][0] + /*rightToLeft*/ time[i][2], i);
      }

      if (!rightBridgeQueue.empty()) {
        // If the bridge is free, the worker waiting on the right side of the
        // bridge gets to cross the bridge. If more than one worker is waiting
        // on the right side, the one with the lowest efficiency crosses first.
        const int i = rightBridgeQueue.top().second;
        rightBridgeQueue.pop();
        ans += /*rightToLeft*/ time[i][2];
        leftWorkers.emplace(ans + /*putNew*/ time[i][3], i);
      } else if (!leftBridgeQueue.empty() && n > 0) {
        // If the bridge is free and no worker is waiting on the right side, and
        // at least one box remains at the old warehouse, the worker on the left
        // side of the river gets to cross the bridge. If more than one worker
        // is waiting on the left side, the one with the lowest efficiency
        // crosses first.
        const int i = leftBridgeQueue.top().second;
        leftBridgeQueue.pop();
        ans += /*leftToRight*/ time[i][0];
        rightWorkers.emplace(ans + /*pickOld*/ time[i][1], i);
        --n;
      } else {
        // Advance the time of the last crossing worker.
        ans = min(
            !leftWorkers.empty() && n > 0 ? leftWorkers.top().first : INT_MAX,
            !rightWorkers.empty() ? rightWorkers.top().first : INT_MAX);
      }
    }

    return ans;
  }
};