Off-by-one on range boundaries
Wrong move: Loop endpoints miss first/last candidate.
Usually fails on: Fails on minimal arrays and exact-boundary answers.
Fix: Re-derive loops from inclusive/exclusive ranges before coding.
Break down a hard problem into reliable checkpoints, edge-case handling, and complexity trade-offs.
There are k workers who want to move n boxes from the right (old) warehouse to the left (new) warehouse. You are given the two integers n and k, and a 2D integer array time of size k x 4 where time[i] = [righti, picki, lefti, puti].
The warehouses are separated by a river and connected by a bridge. Initially, all k workers are waiting on the left side of the bridge. To move the boxes, the ith worker can do the following:
righti minutes.picki minutes.lefti minutes.puti minutes.The ith worker is less efficient than the jth worker if either condition is met:
lefti + righti > leftj + rightjlefti + righti == leftj + rightj and i > jThe following rules regulate the movement of the workers through the bridge:
Return the elapsed minutes at which the last box reaches the left side of the bridge.
Example 1:
Input: n = 1, k = 3, time = [[1,1,2,1],[1,1,3,1],[1,1,4,1]]
Output: 6
Explanation:
From 0 to 1 minutes: worker 2 crosses the bridge to the right. From 1 to 2 minutes: worker 2 picks up a box from the right warehouse. From 2 to 6 minutes: worker 2 crosses the bridge to the left. From 6 to 7 minutes: worker 2 puts a box at the left warehouse. The whole process ends after 7 minutes. We return 6 because the problem asks for the instance of time at which the last worker reaches the left side of the bridge.
Example 2:
Input: n = 3, k = 2, time = [[1,5,1,8],[10,10,10,10]]
Output: 37
Explanation:
The last box reaches the left side at 37 seconds. Notice, how we do not put the last boxes down, as that would take more time, and they are already on the left with the workers.
Constraints:
1 <= n, k <= 104time.length == ktime[i].length == 41 <= lefti, picki, righti, puti <= 1000Problem summary: There are k workers who want to move n boxes from the right (old) warehouse to the left (new) warehouse. You are given the two integers n and k, and a 2D integer array time of size k x 4 where time[i] = [righti, picki, lefti, puti]. The warehouses are separated by a river and connected by a bridge. Initially, all k workers are waiting on the left side of the bridge. To move the boxes, the ith worker can do the following: Cross the bridge to the right side in righti minutes. Pick a box from the right warehouse in picki minutes. Cross the bridge to the left side in lefti minutes. Put the box into the left warehouse in puti minutes. The ith worker is less efficient than the jth worker if either condition is met: lefti + righti > leftj + rightj lefti + righti == leftj + rightj and i > j The following rules regulate the movement of the workers through the bridge: Only one worker can use the
Start with the most direct exhaustive search. That gives a correctness anchor before optimizing.
Pattern signal: Array
1 3 [[1,1,2,1],[1,1,3,1],[1,1,4,1]]
3 2 [[1,9,1,8],[10,10,10,10]]
the-latest-time-to-catch-a-bus)total-cost-to-hire-k-workers)Source-backed implementations are provided below for direct study and interview prep.
// Accepted solution for LeetCode #2532: Time to Cross a Bridge
class Solution {
public int findCrossingTime(int n, int k, int[][] time) {
int[][] t = new int[k][5];
for (int i = 0; i < k; ++i) {
int[] x = time[i];
t[i] = new int[] {x[0], x[1], x[2], x[3], i};
}
Arrays.sort(t, (a, b) -> {
int x = a[0] + a[2], y = b[0] + b[2];
return x == y ? a[4] - b[4] : x - y;
});
int cur = 0;
PriorityQueue<Integer> waitInLeft = new PriorityQueue<>((a, b) -> b - a);
PriorityQueue<Integer> waitInRight = new PriorityQueue<>((a, b) -> b - a);
PriorityQueue<int[]> workInLeft = new PriorityQueue<>((a, b) -> a[0] - b[0]);
PriorityQueue<int[]> workInRight = new PriorityQueue<>((a, b) -> a[0] - b[0]);
for (int i = 0; i < k; ++i) {
waitInLeft.offer(i);
}
while (true) {
while (!workInLeft.isEmpty()) {
int[] p = workInLeft.peek();
if (p[0] > cur) {
break;
}
waitInLeft.offer(workInLeft.poll()[1]);
}
while (!workInRight.isEmpty()) {
int[] p = workInRight.peek();
if (p[0] > cur) {
break;
}
waitInRight.offer(workInRight.poll()[1]);
}
boolean leftToGo = n > 0 && !waitInLeft.isEmpty();
boolean rightToGo = !waitInRight.isEmpty();
if (!leftToGo && !rightToGo) {
int nxt = 1 << 30;
if (!workInLeft.isEmpty()) {
nxt = Math.min(nxt, workInLeft.peek()[0]);
}
if (!workInRight.isEmpty()) {
nxt = Math.min(nxt, workInRight.peek()[0]);
}
cur = nxt;
continue;
}
if (rightToGo) {
int i = waitInRight.poll();
cur += t[i][2];
if (n == 0 && waitInRight.isEmpty() && workInRight.isEmpty()) {
return cur;
}
workInLeft.offer(new int[] {cur + t[i][3], i});
} else {
int i = waitInLeft.poll();
cur += t[i][0];
--n;
workInRight.offer(new int[] {cur + t[i][1], i});
}
}
}
}
// Accepted solution for LeetCode #2532: Time to Cross a Bridge
func findCrossingTime(n int, k int, time [][]int) int {
sort.SliceStable(time, func(i, j int) bool { return time[i][0]+time[i][2] < time[j][0]+time[j][2] })
waitInLeft := hp{}
waitInRight := hp{}
workInLeft := hp2{}
workInRight := hp2{}
for i := range time {
heap.Push(&waitInLeft, i)
}
cur := 0
for {
for len(workInLeft) > 0 {
if workInLeft[0].t > cur {
break
}
heap.Push(&waitInLeft, heap.Pop(&workInLeft).(pair).i)
}
for len(workInRight) > 0 {
if workInRight[0].t > cur {
break
}
heap.Push(&waitInRight, heap.Pop(&workInRight).(pair).i)
}
leftToGo := n > 0 && waitInLeft.Len() > 0
rightToGo := waitInRight.Len() > 0
if !leftToGo && !rightToGo {
nxt := 1 << 30
if len(workInLeft) > 0 {
nxt = min(nxt, workInLeft[0].t)
}
if len(workInRight) > 0 {
nxt = min(nxt, workInRight[0].t)
}
cur = nxt
continue
}
if rightToGo {
i := heap.Pop(&waitInRight).(int)
cur += time[i][2]
if n == 0 && waitInRight.Len() == 0 && len(workInRight) == 0 {
return cur
}
heap.Push(&workInLeft, pair{cur + time[i][3], i})
} else {
i := heap.Pop(&waitInLeft).(int)
cur += time[i][0]
n--
heap.Push(&workInRight, pair{cur + time[i][1], i})
}
}
}
type hp struct{ sort.IntSlice }
func (h hp) Less(i, j int) bool { return h.IntSlice[i] > h.IntSlice[j] }
func (h *hp) Push(v any) { h.IntSlice = append(h.IntSlice, v.(int)) }
func (h *hp) Pop() any {
a := h.IntSlice
v := a[len(a)-1]
h.IntSlice = a[:len(a)-1]
return v
}
type pair struct{ t, i int }
type hp2 []pair
func (h hp2) Len() int { return len(h) }
func (h hp2) Less(i, j int) bool { return h[i].t < h[j].t }
func (h hp2) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
func (h *hp2) Push(v any) { *h = append(*h, v.(pair)) }
func (h *hp2) Pop() any { a := *h; v := a[len(a)-1]; *h = a[:len(a)-1]; return v }
# Accepted solution for LeetCode #2532: Time to Cross a Bridge
class Solution:
def findCrossingTime(self, n: int, k: int, time: List[List[int]]) -> int:
time.sort(key=lambda x: x[0] + x[2])
cur = 0
wait_in_left, wait_in_right = [], []
work_in_left, work_in_right = [], []
for i in range(k):
heappush(wait_in_left, -i)
while 1:
while work_in_left:
t, i = work_in_left[0]
if t > cur:
break
heappop(work_in_left)
heappush(wait_in_left, -i)
while work_in_right:
t, i = work_in_right[0]
if t > cur:
break
heappop(work_in_right)
heappush(wait_in_right, -i)
left_to_go = n > 0 and wait_in_left
right_to_go = bool(wait_in_right)
if not left_to_go and not right_to_go:
nxt = inf
if work_in_left:
nxt = min(nxt, work_in_left[0][0])
if work_in_right:
nxt = min(nxt, work_in_right[0][0])
cur = nxt
continue
if right_to_go:
i = -heappop(wait_in_right)
cur += time[i][2]
if n == 0 and not wait_in_right and not work_in_right:
return cur
heappush(work_in_left, (cur + time[i][3], i))
else:
i = -heappop(wait_in_left)
cur += time[i][0]
n -= 1
heappush(work_in_right, (cur + time[i][1], i))
// Accepted solution for LeetCode #2532: Time to Cross a Bridge
// Rust example auto-generated from java reference.
// Replace the signature and local types with the exact LeetCode harness for this problem.
impl Solution {
pub fn rust_example() {
// Port the logic from the reference block below.
}
}
// Reference (java):
// // Accepted solution for LeetCode #2532: Time to Cross a Bridge
// class Solution {
// public int findCrossingTime(int n, int k, int[][] time) {
// int[][] t = new int[k][5];
// for (int i = 0; i < k; ++i) {
// int[] x = time[i];
// t[i] = new int[] {x[0], x[1], x[2], x[3], i};
// }
// Arrays.sort(t, (a, b) -> {
// int x = a[0] + a[2], y = b[0] + b[2];
// return x == y ? a[4] - b[4] : x - y;
// });
// int cur = 0;
// PriorityQueue<Integer> waitInLeft = new PriorityQueue<>((a, b) -> b - a);
// PriorityQueue<Integer> waitInRight = new PriorityQueue<>((a, b) -> b - a);
// PriorityQueue<int[]> workInLeft = new PriorityQueue<>((a, b) -> a[0] - b[0]);
// PriorityQueue<int[]> workInRight = new PriorityQueue<>((a, b) -> a[0] - b[0]);
// for (int i = 0; i < k; ++i) {
// waitInLeft.offer(i);
// }
// while (true) {
// while (!workInLeft.isEmpty()) {
// int[] p = workInLeft.peek();
// if (p[0] > cur) {
// break;
// }
// waitInLeft.offer(workInLeft.poll()[1]);
// }
// while (!workInRight.isEmpty()) {
// int[] p = workInRight.peek();
// if (p[0] > cur) {
// break;
// }
// waitInRight.offer(workInRight.poll()[1]);
// }
// boolean leftToGo = n > 0 && !waitInLeft.isEmpty();
// boolean rightToGo = !waitInRight.isEmpty();
// if (!leftToGo && !rightToGo) {
// int nxt = 1 << 30;
// if (!workInLeft.isEmpty()) {
// nxt = Math.min(nxt, workInLeft.peek()[0]);
// }
// if (!workInRight.isEmpty()) {
// nxt = Math.min(nxt, workInRight.peek()[0]);
// }
// cur = nxt;
// continue;
// }
// if (rightToGo) {
// int i = waitInRight.poll();
// cur += t[i][2];
// if (n == 0 && waitInRight.isEmpty() && workInRight.isEmpty()) {
// return cur;
// }
// workInLeft.offer(new int[] {cur + t[i][3], i});
// } else {
// int i = waitInLeft.poll();
// cur += t[i][0];
// --n;
// workInRight.offer(new int[] {cur + t[i][1], i});
// }
// }
// }
// }
// Accepted solution for LeetCode #2532: Time to Cross a Bridge
// Auto-generated TypeScript example from java.
function exampleSolution(): void {
}
// Reference (java):
// // Accepted solution for LeetCode #2532: Time to Cross a Bridge
// class Solution {
// public int findCrossingTime(int n, int k, int[][] time) {
// int[][] t = new int[k][5];
// for (int i = 0; i < k; ++i) {
// int[] x = time[i];
// t[i] = new int[] {x[0], x[1], x[2], x[3], i};
// }
// Arrays.sort(t, (a, b) -> {
// int x = a[0] + a[2], y = b[0] + b[2];
// return x == y ? a[4] - b[4] : x - y;
// });
// int cur = 0;
// PriorityQueue<Integer> waitInLeft = new PriorityQueue<>((a, b) -> b - a);
// PriorityQueue<Integer> waitInRight = new PriorityQueue<>((a, b) -> b - a);
// PriorityQueue<int[]> workInLeft = new PriorityQueue<>((a, b) -> a[0] - b[0]);
// PriorityQueue<int[]> workInRight = new PriorityQueue<>((a, b) -> a[0] - b[0]);
// for (int i = 0; i < k; ++i) {
// waitInLeft.offer(i);
// }
// while (true) {
// while (!workInLeft.isEmpty()) {
// int[] p = workInLeft.peek();
// if (p[0] > cur) {
// break;
// }
// waitInLeft.offer(workInLeft.poll()[1]);
// }
// while (!workInRight.isEmpty()) {
// int[] p = workInRight.peek();
// if (p[0] > cur) {
// break;
// }
// waitInRight.offer(workInRight.poll()[1]);
// }
// boolean leftToGo = n > 0 && !waitInLeft.isEmpty();
// boolean rightToGo = !waitInRight.isEmpty();
// if (!leftToGo && !rightToGo) {
// int nxt = 1 << 30;
// if (!workInLeft.isEmpty()) {
// nxt = Math.min(nxt, workInLeft.peek()[0]);
// }
// if (!workInRight.isEmpty()) {
// nxt = Math.min(nxt, workInRight.peek()[0]);
// }
// cur = nxt;
// continue;
// }
// if (rightToGo) {
// int i = waitInRight.poll();
// cur += t[i][2];
// if (n == 0 && waitInRight.isEmpty() && workInRight.isEmpty()) {
// return cur;
// }
// workInLeft.offer(new int[] {cur + t[i][3], i});
// } else {
// int i = waitInLeft.poll();
// cur += t[i][0];
// --n;
// workInRight.offer(new int[] {cur + t[i][1], i});
// }
// }
// }
// }
Use this to step through a reusable interview workflow for this problem.
Two nested loops check every pair or subarray. The outer loop fixes a starting point, the inner loop extends or searches. For n elements this gives up to n²/2 operations. No extra space, but the quadratic time is prohibitive for large inputs.
Most array problems have an O(n²) brute force (nested loops) and an O(n) optimal (single pass with clever state tracking). The key is identifying what information to maintain as you scan: a running max, a prefix sum, a hash map of seen values, or two pointers.
Review these before coding to avoid predictable interview regressions.
Wrong move: Loop endpoints miss first/last candidate.
Usually fails on: Fails on minimal arrays and exact-boundary answers.
Fix: Re-derive loops from inclusive/exclusive ranges before coding.