Facebook Pixel

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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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.

Learn more about Greedy and Sorting patterns.

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 longest
  • workers[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 rate b 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 Evaluator

Example 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 and max 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)) uses O(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)
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

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

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More