2323. Find Minimum Time to Finish All Jobs II π
Problem Description
You have two arrays of equal length: jobs
and workers
. Each element jobs[i]
represents the time needed to complete the i-th job, and each element workers[j]
represents how many hours per day the j-th worker can work.
You need to assign each job to exactly one worker, with the constraint that:
- Each job must be assigned to exactly one worker
- Each worker must be assigned exactly one job
When a worker is assigned a job, they will work on it for their daily working hours until the job is complete. For example, if a job requires 10 hours and a worker can work 3 hours per day, it will take β10/3β = 4
days to complete.
The goal is to find an assignment that minimizes the maximum number of days any worker needs to complete their assigned job. This represents the total time needed to complete all jobs (since workers work in parallel).
The solution uses a greedy approach: sort both arrays and match them by index (smallest job to smallest capacity worker, etc.). Then calculate the days needed for each pair as βjobs[i] / workers[i]β
and return the maximum among all pairs.
Intuition
The key insight is understanding what determines the total completion time. Since all workers work in parallel, the total time is determined by the worker who takes the longest to finish their job.
Consider what happens with different assignment strategies. If we assign a very long job to a worker with low daily capacity, that worker will take many days to complete it. Conversely, if we assign short jobs to workers with high capacity, they'll finish quickly but might be underutilized.
To minimize the maximum completion time, we want to avoid creating any particularly bad pairings. The worst case would be pairing the longest job with the slowest worker - this would create a bottleneck that determines the overall completion time.
This leads us to think about balance: we should match longer jobs with workers who have higher daily capacity, and shorter jobs with workers who have lower daily capacity. This way, the completion times across all worker-job pairs tend to be more uniform, avoiding extreme outliers.
Why does sorting and matching by index work? When both arrays are sorted, we're essentially creating a balanced assignment where:
- The hardest job goes to the most capable worker
- The easiest job goes to the least capable worker
- Everything in between follows this pattern
This greedy matching ensures no single worker-job pair becomes an excessive bottleneck. The formula (jobs[i] + workers[i] - 1) // workers[i]
calculates the ceiling of the division, giving us the exact number of days needed for each pairing, and we return the maximum among all pairs since that determines when all jobs are complete.
Solution Approach
The implementation follows a greedy strategy with these steps:
Step 1: Sort both arrays
jobs.sort() workers.sort()
By sorting both jobs
and workers
in ascending order, we prepare them for optimal pairing. After sorting:
jobs[0]
is the shortest job,jobs[-1]
is the longestworkers[0]
has the lowest daily capacity,workers[-1]
has the highest
Step 2: Pair jobs with workers by index
zip(jobs, workers)
The zip
function pairs elements at the same index from both sorted arrays. This creates the balanced assignment where:
jobs[0]
(shortest) βworkers[0]
(lowest capacity)jobs[1]
βworkers[1]
- ...
jobs[n-1]
(longest) βworkers[n-1]
(highest capacity)
Step 3: Calculate days needed for each pair
(a + b - 1) // b
For each job-worker pair (a, b)
where a
is the job time and b
is the worker's daily capacity, we calculate the number of days needed. The expression (a + b - 1) // b
computes the ceiling of a/b
using integer arithmetic:
- This is equivalent to
math.ceil(a/b)
- It calculates how many full days of work are needed to complete job
a
at rateb
per day
Step 4: Return the maximum days
max(...)
Since all workers work in parallel, the total completion time is determined by the worker who takes the longest. We return the maximum number of days among all worker-job pairs.
The time complexity is O(n log n)
due to sorting, where n
is the length of the arrays. The space complexity is O(1)
if we don't count the space used for sorting.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example to illustrate the solution approach.
Given:
jobs = [5, 2, 4]
(hours needed for each job)workers = [1, 7, 5]
(hours per day each worker can work)
Step 1: Sort both arrays
- After sorting:
jobs = [2, 4, 5]
- After sorting:
workers = [1, 5, 7]
Step 2: Create paired assignments by index
- Job 0 (2 hours) β Worker 0 (1 hour/day)
- Job 1 (4 hours) β Worker 1 (5 hours/day)
- Job 2 (5 hours) β Worker 2 (7 hours/day)
Step 3: Calculate days needed for each pairing
- Worker 0 needs:
β2/1β = 2
days - Worker 1 needs:
β4/5β = 1
day - Worker 2 needs:
β5/7β = 1
day
Using the formula (a + b - 1) // b
:
- Worker 0:
(2 + 1 - 1) // 1 = 2 // 1 = 2
days - Worker 1:
(4 + 5 - 1) // 5 = 8 // 5 = 1
day - Worker 2:
(5 + 7 - 1) // 7 = 11 // 7 = 1
day
Step 4: Find the maximum
- Maximum among [2, 1, 1] = 2 days
Result: The minimum possible maximum completion time is 2 days.
Notice how the greedy approach avoids bad pairings. If we had instead assigned the longest job (5 hours) to the slowest worker (1 hour/day), that would take 5 days - much worse than our optimal solution of 2 days.
Solution Implementation
1class Solution:
2 def minimumTime(self, jobs: List[int], workers: List[int]) -> int:
3 """
4 Calculate the minimum time needed for workers to complete all jobs in parallel.
5
6 Each worker can work on exactly one job, and the time taken by worker i to complete
7 job j is ceiling(jobs[j] / workers[i]) days.
8
9 Args:
10 jobs: List of job sizes/workloads
11 workers: List of worker efficiencies/speeds
12
13 Returns:
14 Minimum number of days needed to complete all jobs
15 """
16 # Sort both arrays to optimally pair jobs with workers
17 # Smallest jobs go to slowest workers, largest jobs to fastest workers
18 jobs.sort()
19 workers.sort()
20
21 # Calculate the maximum time among all worker-job pairs
22 # For each job-worker pair, compute ceiling division: (job + worker - 1) // worker
23 # This formula calculates ceiling(job / worker) without using math.ceil()
24 max_time = 0
25 for job_size, worker_speed in zip(jobs, workers):
26 # Calculate time for this worker to complete this job (ceiling division)
27 time_needed = (job_size + worker_speed - 1) // worker_speed
28 # Track the maximum time across all assignments
29 max_time = max(max_time, time_needed)
30
31 return max_time
32
1class Solution {
2 public int minimumTime(int[] jobs, int[] workers) {
3 // Sort both arrays to optimally pair jobs with workers
4 Arrays.sort(jobs);
5 Arrays.sort(workers);
6
7 // Track the maximum time needed among all worker-job pairs
8 int maxTime = 0;
9
10 // Iterate through each job-worker pair
11 for (int i = 0; i < jobs.length; ++i) {
12 // Calculate time needed for current worker to complete current job
13 // Using ceiling division: ceil(jobs[i] / workers[i])
14 int timeForCurrentPair = (jobs[i] + workers[i] - 1) / workers[i];
15
16 // Update maximum time if current pair takes longer
17 maxTime = Math.max(maxTime, timeForCurrentPair);
18 }
19
20 return maxTime;
21 }
22}
23
1class Solution {
2public:
3 int minimumTime(vector<int>& jobs, vector<int>& workers) {
4 // Sort both arrays to optimally pair jobs with workers
5 sort(jobs.begin(), jobs.end());
6 sort(workers.begin(), workers.end());
7
8 // Track the maximum time needed among all worker-job pairs
9 int maxTime = 0;
10 int n = jobs.size();
11
12 // Pair each job with a worker and calculate the time needed
13 for (int i = 0; i < n; ++i) {
14 // Calculate ceiling division: ceil(jobs[i] / workers[i])
15 // This represents the time needed for worker[i] to complete job[i]
16 int timeNeeded = (jobs[i] + workers[i] - 1) / workers[i];
17
18 // Update the maximum time if current pair takes longer
19 maxTime = max(maxTime, timeNeeded);
20 }
21
22 return maxTime;
23 }
24};
25
1/**
2 * Calculates the minimum time needed for workers to complete all jobs
3 * @param jobs - Array of job difficulties/requirements
4 * @param workers - Array of worker capabilities/speeds
5 * @returns The minimum time needed to complete all jobs
6 */
7function minimumTime(jobs: number[], workers: number[]): number {
8 // Sort jobs in ascending order
9 jobs.sort((a: number, b: number) => a - b);
10
11 // Sort workers in ascending order
12 workers.sort((a: number, b: number) => a - b);
13
14 // Initialize the maximum time needed
15 let maxTime: number = 0;
16
17 // Get the number of jobs (assuming equal number of jobs and workers)
18 const jobCount: number = jobs.length;
19
20 // Calculate time for each job-worker pair
21 // Each job is assigned to a worker at the same index after sorting
22 for (let i: number = 0; i < jobCount; i++) {
23 // Calculate time needed for current job-worker pair (rounded up)
24 const currentTime: number = Math.ceil(jobs[i] / workers[i]);
25
26 // Update maximum time if current time is greater
27 maxTime = Math.max(maxTime, currentTime);
28 }
29
30 // Return the minimum time needed (which is the maximum among all pairs)
31 return maxTime;
32}
33
Time and Space Complexity
The time complexity is O(n log n)
, where n
is the number of jobs (which equals the number of workers based on the problem context). This complexity comes from:
- Sorting the
jobs
array:O(n log n)
- Sorting the
workers
array:O(n log n)
- The
zip
andmax
operations with the generator expression:O(n)
- Overall:
O(n log n) + O(n log n) + O(n) = O(n log n)
The space complexity is O(log n)
. This comes from:
- The sorting operations use
O(log n)
space for the recursive call stack in typical implementations like Timsort (Python's default sorting algorithm) - The generator expression
((a + b - 1) // b for a, b in zip(jobs, workers))
usesO(1)
extra space as it doesn't create a list but generates values on-the-fly - Overall:
O(log n)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Ceiling Division Formula
A common mistake is using regular integer division job_size // worker_speed
instead of ceiling division. This would underestimate the days needed.
Wrong approach:
time_needed = job_size // worker_speed # This rounds down!
Correct approach:
time_needed = (job_size + worker_speed - 1) // worker_speed # OR import math time_needed = math.ceil(job_size / worker_speed)
2. Misunderstanding the Greedy Pairing Logic
Some might think pairing the largest job with the fastest worker (reverse pairing) would be optimal, but this can lead to suboptimal solutions.
Wrong pairing:
jobs.sort() workers.sort(reverse=True) # Wrong! This pairs smallest job with fastest worker
Why the correct pairing works: The balanced approach (small-to-small, large-to-large) prevents extreme mismatches. Consider:
- Jobs: [5, 10], Workers: [1, 10]
- Correct pairing: (5β1)=5 days, (10β10)=1 day, max=5 days
- Wrong pairing: (5β10)=1 day, (10β1)=10 days, max=10 days
3. Not Handling Edge Cases
Failing to consider when worker capacity is very large compared to job size.
Potential issue:
# If worker_speed > job_size, the result should be 1 day # The formula (job_size + worker_speed - 1) // worker_speed handles this correctly
4. Using Float Division Unnecessarily
Using floating-point arithmetic when integer arithmetic suffices can introduce precision issues.
Less efficient:
import math
time_needed = int(math.ceil(float(job_size) / float(worker_speed)))
Better approach:
time_needed = (job_size + worker_speed - 1) // worker_speed
5. Modifying Input Arrays Without Consideration
The solution sorts the input arrays in-place, which modifies the original data. If the original order needs to be preserved elsewhere, create copies first.
Safe approach if original order matters:
jobs_sorted = sorted(jobs)
workers_sorted = sorted(workers)
Given a sorted array of integers and an integer called target, find the element that
equals to the target and return its index. Select the correct code that fills the
___
in the given code snippet.
1def binary_search(arr, target):
2 left, right = 0, len(arr) - 1
3 while left ___ right:
4 mid = (left + right) // 2
5 if arr[mid] == target:
6 return mid
7 if arr[mid] < target:
8 ___ = mid + 1
9 else:
10 ___ = mid - 1
11 return -1
12
1public static int binarySearch(int[] arr, int target) {
2 int left = 0;
3 int right = arr.length - 1;
4
5 while (left ___ right) {
6 int mid = left + (right - left) / 2;
7 if (arr[mid] == target) return mid;
8 if (arr[mid] < target) {
9 ___ = mid + 1;
10 } else {
11 ___ = mid - 1;
12 }
13 }
14 return -1;
15}
16
1function binarySearch(arr, target) {
2 let left = 0;
3 let right = arr.length - 1;
4
5 while (left ___ right) {
6 let mid = left + Math.trunc((right - left) / 2);
7 if (arr[mid] == target) return mid;
8 if (arr[mid] < target) {
9 ___ = mid + 1;
10 } else {
11 ___ = mid - 1;
12 }
13 }
14 return -1;
15}
16
Recommended Readings
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Donβt Miss This!