LeetCode 2323 - Find Minimum Time to Finish All Jobs II
This problem asks us to assign jobs to workers in a way that minimizes the total number of days needed to finish all jobs. We are given two integer arrays of equal length: - jobs[i] represents the amount of work required for the i-th job.
Difficulty: 🟡 Medium
Topics: Array, Greedy, Sorting
Solution
Problem Understanding
This problem asks us to assign jobs to workers in a way that minimizes the total number of days needed to finish all jobs.
We are given two integer arrays of equal length:
jobs[i]represents the amount of work required for thei-thjob.workers[j]represents how much work thej-thworker can complete in one day.
Each worker must be assigned exactly one job, and each job must be assigned to exactly one worker. Once a worker is assigned a job, the number of days required for that worker to finish the job is:
$$\lceil \frac{\text{job}}{\text{worker}} \rceil$$
This means if a worker can complete 5 units per day and the job requires 12 units, it takes:
$$\lceil 12/5 \rceil = 3$$
days.
The objective is to minimize the maximum number of days required among all assignments, because all workers operate independently and jobs finish when the slowest assignment finishes.
The constraints are important:
ncan be as large as10^5- Job sizes and worker capacities can also reach
10^5
Since n is large, any factorial or quadratic assignment strategy is far too slow. We need an algorithm around O(n log n) to comfortably handle the input size.
There are also several guarantees in the problem:
- The arrays always have equal length.
- Every worker and job has a positive value, so division-by-zero concerns do not exist.
- Every worker must receive exactly one job.
An important observation is that a poor assignment can dramatically increase the maximum completion time. For example, assigning the strongest worker to the easiest job and the weakest worker to the hardest job can create unnecessarily large completion times.
A few edge cases stand out immediately. If there is only one worker and one job, the answer is simply the ceiling division of that job by that worker. If all workers are equally strong, then only job ordering matters. If all jobs are identical, pairing choices still matter because weaker workers may become bottlenecks. Large mismatches between worker strength and job size can expose flaws in greedy reasoning.
Approaches
Brute Force Approach
A brute-force solution would try every possible assignment of workers to jobs.
Since each worker must be assigned exactly one unique job, this becomes a permutation problem. For every possible worker-job pairing configuration:
- Compute the required days for each assigned pair.
- Find the maximum days among those assignments.
- Keep the minimum maximum value across all permutations.
This guarantees correctness because every valid assignment is examined.
However, the number of assignments is:
$$n!$$
For n = 10^5, this is astronomically large and completely infeasible.
Even for n = 15, brute force is already impractical.
Key Insight for an Optimal Solution
The key insight comes from a greedy matching principle.
Suppose we want to minimize the worst completion time. If we assign a very large job to a weak worker while a strong worker is handling an easy job, the weak worker becomes a bottleneck.
To reduce the maximum completion time, we should pair:
- smaller jobs with weaker workers
- larger jobs with stronger workers
This is an example of a greedy matching strategy similar to minimizing maximum pair cost.
We sort both arrays:
- Sort
jobsin ascending order. - Sort
workersin ascending order.
Then pair elements at the same indices.
For each pair, compute:
$$\left\lceil \frac{\text{job}}{\text{worker}} \right\rceil$$
The answer is the maximum of these values.
Why does sorting work?
If two workers are out of order relative to job difficulty, swapping them cannot make the worst completion time larger, and often improves it. This is a classic exchange argument. Pairing strongest workers with hardest jobs minimizes imbalance.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n! × n) | O(n) | Tries every assignment permutation |
| Optimal Greedy + Sorting | O(n log n) | O(1) or O(log n) | Sort both arrays and pair corresponding elements |
Algorithm Walkthrough
- Sort the
jobsarray in ascending order.
We want smaller jobs to appear first and larger jobs later. This creates a natural progression of difficulty.
2. Sort the workers array in ascending order.
This places weaker workers first and stronger workers later. 3. Pair jobs and workers at the same index.
The weakest worker receives the smallest job, the second weakest worker receives the second smallest job, and so on. 4. For each pair, compute the required number of days.
Since a worker may not perfectly divide the job size, we use ceiling division:
$$\left\lceil \frac{job}{worker} \right\rceil$$
In integer arithmetic:
(job + worker - 1) // worker
This avoids floating-point precision issues. 5. Track the maximum number of days.
Since all jobs run independently, total completion time is determined by the slowest assignment. 6. Return the maximum value found.
Why it works
The correctness comes from a greedy exchange argument. If a larger job is assigned to a weaker worker while a smaller job is assigned to a stronger worker, swapping those assignments cannot increase the maximum completion time. Repeatedly applying this reasoning leads to an arrangement where jobs and workers are both sorted and paired in order. This minimizes the worst-case completion time across all assignments.
Python Solution
from typing import List
class Solution:
def minimumTime(self, jobs: List[int], workers: List[int]) -> int:
jobs.sort()
workers.sort()
minimum_days = 0
for job, worker in zip(jobs, workers):
days_needed = (job + worker - 1) // worker
minimum_days = max(minimum_days, days_needed)
return minimum_days
The implementation begins by sorting both arrays. This establishes the optimal pairing order where weaker workers receive smaller jobs and stronger workers receive larger jobs.
After sorting, we iterate through both arrays simultaneously using zip. For every (job, worker) pair, we calculate the number of days needed using ceiling division:
(job + worker - 1) // worker
This formula correctly computes the ceiling without floating-point arithmetic.
We continuously track the largest number of days because the overall completion time is determined by the slowest worker-job assignment. Finally, we return that maximum.
Go Solution
package main
import "sort"
func minimumTime(jobs []int, workers []int) int {
sort.Ints(jobs)
sort.Ints(workers)
maxDays := 0
for i := 0; i < len(jobs); i++ {
daysNeeded := (jobs[i] + workers[i] - 1) / workers[i]
if daysNeeded > maxDays {
maxDays = daysNeeded
}
}
return maxDays
}
The Go implementation follows exactly the same logic as the Python version. We sort both slices using sort.Ints, iterate through matching indices, compute ceiling division using integer arithmetic, and maintain the maximum value.
Go does not require special handling for integer overflow here because the constraints are small enough. The maximum intermediate value is at most:
$$10^5 + 10^5 - 1$$
which easily fits inside Go's int type.
Since slices are modified in place, the original ordering is overwritten, just like Python's in-place .sort().
Worked Examples
Example 1
Input:
jobs = [5,2,4]
workers = [1,7,5]
Step 1: Sort both arrays
| Array | Result |
|---|---|
| jobs | [2, 4, 5] |
| workers | [1, 5, 7] |
Step 2: Pair and compute days
| Job | Worker | Calculation | Days | Current Maximum |
|---|---|---|---|---|
| 2 | 1 | ceil(2/1) | 2 | 2 |
| 4 | 5 | ceil(4/5) | 1 | 2 |
| 5 | 7 | ceil(5/7) | 1 | 2 |
Final answer:
2
Example 2
Input:
jobs = [3,18,15,9]
workers = [6,5,1,3]
Step 1: Sort both arrays
| Array | Result |
|---|---|
| jobs | [3, 9, 15, 18] |
| workers | [1, 3, 5, 6] |
Step 2: Pair and compute days
| Job | Worker | Calculation | Days | Current Maximum |
|---|---|---|---|---|
| 3 | 1 | ceil(3/1) | 3 | 3 |
| 9 | 3 | ceil(9/3) | 3 | 3 |
| 15 | 5 | ceil(15/5) | 3 | 3 |
| 18 | 6 | ceil(18/6) | 3 | 3 |
Final answer:
3
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n log n) | Sorting both arrays dominates runtime |
| Space | O(1) or O(log n) | Sorting is in place, recursion stack may be used internally |
The dominant operation is sorting the two arrays, which takes O(n log n) time. The final traversal to compute the maximum completion time is linear, O(n).
Space complexity is effectively constant if we ignore sorting internals. However, practical sorting algorithms may use logarithmic recursion stack space.
Test Cases
solution = Solution()
assert solution.minimumTime([5, 2, 4], [1, 7, 5]) == 2 # Example 1
assert solution.minimumTime([3, 18, 15, 9], [6, 5, 1, 3]) == 3 # Example 2
assert solution.minimumTime([10], [2]) == 5 # Single worker/job
assert solution.minimumTime([1], [100]) == 1 # Worker stronger than job
assert solution.minimumTime([5, 5, 5], [5, 5, 5]) == 1 # Equal jobs/workers
assert solution.minimumTime([10, 10, 10], [1, 2, 10]) == 5 # Mixed worker strengths
assert solution.minimumTime([100000], [1]) == 100000 # Maximum job size
assert solution.minimumTime([1, 100000], [1, 100000]) == 1 # Perfect matching
assert solution.minimumTime([8, 16, 24], [2, 4, 8]) == 4 # Increasing scale
assert solution.minimumTime([9, 1, 4], [3, 10, 2]) == 2 # Unsorted inputs
| Test | Why |
|---|---|
[5,2,4], [1,7,5] |
Validates Example 1 |
[3,18,15,9], [6,5,1,3] |
Validates Example 2 |
[10], [2] |
Minimum input size |
[1], [100] |
Worker stronger than required |
[5,5,5], [5,5,5] |
Uniform values |
[10,10,10], [1,2,10] |
Different worker capacities |
[100000], [1] |
Maximum constraint value |
[1,100000], [1,100000] |
Perfectly balanced matching |
[8,16,24], [2,4,8] |
Scaling behavior |
[9,1,4], [3,10,2] |
Confirms sorting is necessary |
Edge Cases
One important edge case is when there is only a single job and worker. A naive implementation might overcomplicate the logic or mishandle indexing. Since the arrays always have equal length, our implementation naturally handles this case because sorting a single-element array changes nothing, and the loop executes exactly once.
Another important edge case occurs when workers are significantly stronger than the assigned jobs. For example, jobs = [1, 2, 3] and workers = [100, 100, 100]. Integer division can be a source of bugs here because standard division would incorrectly produce 0 if floor division were used directly. Our ceiling division formula ensures every nonzero job takes at least one day.
A third important edge case is highly imbalanced worker strength. Consider jobs = [10, 10, 10] and workers = [1, 2, 100]. A poor assignment strategy could pair the strongest worker with an easy job and leave the weakest worker overloaded, increasing the maximum completion time. Sorting guarantees balanced matching, minimizing bottlenecks.
Finally, unsorted input is an important practical case. The problem does not guarantee any ordering for either array. Since our implementation explicitly sorts both arrays before pairing, it always computes the optimal assignment regardless of input order.