A custom binary-search-on-answer problem where the search shell is familiar, but the feasibility check becomes a greedy contiguous-block assignment.
Given an integer array tasks where tasks[i] is the work required for task i, and an array speeds of worker speeds, assign contiguous blocks of tasks to workers. A worker with speed s assigned total work W finishes in time W / s. Return the minimum possible time needed to finish all tasks.
We are not searching for a task assignment directly. We binary search a candidate time limit T and ask one yes/no question: if every worker is allowed at most T time, can the tasks be covered in order?
If time limit T works, every larger time also works. That false-to-true transition is exactly the shape binary search needs.
Walk left to right through tasks. For a candidate time T, worker i can cover at most T × speeds[i] total work. Greedily give that worker as many consecutive tasks as fit; once the next task would overflow the budget, move to the next worker.
| Worker | Speed | Budget at T=5 | Tasks taken | Work used | Status |
|---|---|---|---|---|---|
| A | 2 | 10 | [3, 2, 5] | 10 | Fits |
| B | 1 | 5 | [4, 1] | 5 | Fits |
| C | 3 | 15 | [6] | 6 | Fits |
Why greedy is correct: task order is fixed and assignments must be contiguous. Once a worker stops at task i, the next worker must start at i+1. There is no better rearrangement to search for, so greedily filling each worker’s budget is optimal for the feasibility check.
Closest LeetCode relative: this is structurally closest to LC 410 — Split Array Largest Sum. Both minimize the maximum load across contiguous blocks; this version adds worker speeds and makes the answer a floating-point time bound.
class Solution { public double minTime(int[] tasks, int[] speeds) { double left = 0.0, right = 0.0; for (int t : tasks) right += t; // speed 1 worker is a safe upper bound double eps = 1e-9; while (right - left > eps) { double mid = left + (right - left) / 2.0; if (canFinish(tasks, speeds, mid)) { right = mid; // feasible — try tighter } else { left = mid; // too small — need more time } } return right; } private boolean canFinish(int[] tasks, int[] speeds, double timeLimit) { int taskIdx = 0; for (int speed : speeds) { double budget = timeLimit * speed; double used = 0.0; while (taskIdx < tasks.length && used + tasks[taskIdx] <= budget) { used += tasks[taskIdx]; taskIdx++; } if (taskIdx == tasks.length) return true; } return false; } }
class Solution: def min_time(self, tasks: list[int], speeds: list[int]) -> float: left, right = 0.0, float(sum(tasks)) eps = 1e-9 while right - left > eps: mid = left + (right - left) / 2.0 if self.can_finish(tasks, speeds, mid): right = mid else: left = mid return right def can_finish(self, tasks, speeds, time_limit): task_idx = 0 for speed in speeds: budget = time_limit * speed used = 0.0 while task_idx < len(tasks) and used + tasks[task_idx] <= budget: used += tasks[task_idx] task_idx += 1 if task_idx == len(tasks): return True return False
Why floating-point binary search? the optimal time can be fractional, because a worker may finish a block in W / speed hours. So we search with an epsilon instead of integer boundaries.