Facebook Pixel

857. Minimum Cost to Hire K Workers

Problem Description

You have n workers, each with a quality value and a minimum wage expectation. The goal is to hire exactly k workers while minimizing the total cost.

The hiring rules are:

  1. Each hired worker must receive at least their minimum wage expectation
  2. Within the hired group, pay must be proportional to quality (if worker A has twice the quality of worker B, then worker A must be paid exactly twice as much)

The key insight is that once you form a group, the pay ratio is determined by the worker with the highest wage-to-quality ratio in that group. This "captain" sets the wage rate for everyone else.

The solution sorts workers by their wage/quality ratio. For each potential captain (worker with highest ratio), it considers forming a group with that captain and the k-1 workers with lowest quality among those with equal or lower wage/quality ratios. This ensures all workers meet their minimum wage requirement.

The algorithm uses a max heap to track the k workers with smallest quality. For each captain position, it calculates the total cost as (captain's wage/quality ratio) × (sum of k workers' qualities). The heap helps efficiently maintain the k smallest qualities as we iterate through potential captains.

The time complexity is O(n log n) for sorting and heap operations, and space complexity is O(n) for storing the sorted array and heap.

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

Intuition

Let's think about what happens when we hire a group of workers. Since everyone's pay must be proportional to their quality, we can express each worker's pay as pay[i] = quality[i] × rate, where rate is the same for all workers in the group.

For each worker to meet their minimum wage expectation, we need quality[i] × rate ≥ wage[i], which means rate ≥ wage[i] / quality[i].

Here's the crucial observation: the rate for the entire group must be at least the maximum wage[i] / quality[i] ratio among all workers in the group. This worker with the highest ratio becomes the "bottleneck" - they determine the minimum rate everyone must be paid at.

So if we fix a particular rate (determined by some worker as the bottleneck), the total cost becomes rate × (sum of qualities of k workers). To minimize this cost for a fixed rate, we want to choose the k workers with the smallest qualities.

But which workers can we actually choose? We can only include workers whose wage/quality ratio is less than or equal to our chosen rate. Otherwise, they wouldn't meet their minimum wage expectation.

This leads us to the strategy:

  1. Sort workers by their wage/quality ratio
  2. Consider each worker as a potential "captain" (the one setting the rate)
  3. For each captain, form a group with them and the k-1 workers with smallest qualities among those who appear before them in the sorted order (these have lower or equal ratios)
  4. Calculate the cost for each such group and keep track of the minimum

The sorting ensures that when we consider a worker as captain, all previous workers in the sorted list are eligible to be in their group. We use a heap to efficiently maintain the k smallest qualities as we iterate through potential captains.

Learn more about Greedy, Sorting and Heap (Priority Queue) patterns.

Solution Approach

The implementation follows our intuition step by step:

  1. Sort workers by wage/quality ratio: We create tuples of (quality, wage) and sort them by wage/quality ratio using a lambda function lambda x: x[1] / x[0]. This ensures workers are ordered from lowest to highest ratio.

  2. Initialize variables:

    • ans = inf: Tracks the minimum cost found so far
    • tot = 0: Maintains the sum of qualities in our current group
    • h = []: A max heap (using negative values in Python's min heap) to store qualities
  3. Iterate through sorted workers: For each worker (q, w) in sorted order:

    • Add their quality to the running total: tot += q
    • Push their negative quality to the heap: heappush(h, -q) (negative because Python only has min heap)
  4. When we have k workers (len(h) == k):

    • Calculate cost with current worker as captain: w / q * tot
      • w / q is the captain's wage/quality ratio (the rate)
      • tot is the sum of k smallest qualities
      • Their product gives the total cost
    • Update minimum cost: ans = min(ans, w / q * tot)
    • Remove the worker with highest quality to make room for next iteration: tot += heappop(h)
      • We pop the largest quality (most negative value in heap)
      • Adding it back to tot effectively removes it from the sum
  5. Return the minimum cost found across all valid groups.

The key insight is that as we iterate through workers in increasing wage/quality ratio order, each worker becomes the new captain (highest ratio in group). The heap maintains the k workers with smallest qualities that can be paired with this captain, ensuring we always compute the minimum possible cost for each captain choice.

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 small example with n = 4 workers and we want to hire k = 2:

  • Worker 0: quality = 10, wage = 70 (ratio = 7.0)
  • Worker 1: quality = 20, wage = 50 (ratio = 2.5)
  • Worker 2: quality = 5, wage = 30 (ratio = 6.0)
  • Worker 3: quality = 4, wage = 28 (ratio = 7.0)

Step 1: Sort by wage/quality ratio After sorting:

  • Worker 1: quality = 20, wage = 50 (ratio = 2.5)
  • Worker 2: quality = 5, wage = 30 (ratio = 6.0)
  • Worker 0: quality = 10, wage = 70 (ratio = 7.0)
  • Worker 3: quality = 4, wage = 28 (ratio = 7.0)

Step 2: Iterate through sorted workers

Iteration 1 (Worker 1 as potential captain):

  • Add quality 20 to heap: h = [-20], tot = 20
  • Heap size < k, continue

Iteration 2 (Worker 2 as captain):

  • Add quality 5 to heap: h = [-20, -5], tot = 25
  • Heap size = k = 2, calculate cost:
    • Captain's ratio: 30/5 = 6.0
    • Total qualities: 25
    • Cost = 6.0 × 25 = 150
  • Update ans = 150
  • Remove largest quality (20): tot = 25 - 20 = 5

Iteration 3 (Worker 0 as captain):

  • Add quality 10 to heap: h = [-10, -5], tot = 15
  • Heap size = k = 2, calculate cost:
    • Captain's ratio: 70/10 = 7.0
    • Total qualities: 15
    • Cost = 7.0 × 15 = 105
  • Update ans = min(150, 105) = 105
  • Remove largest quality (10): tot = 15 - 10 = 5

Iteration 4 (Worker 3 as captain):

  • Add quality 4 to heap: h = [-5, -4], tot = 9
  • Heap size = k = 2, calculate cost:
    • Captain's ratio: 28/4 = 7.0
    • Total qualities: 9
    • Cost = 7.0 × 9 = 63
  • Update ans = min(105, 63) = 63

Final answer: 63

The optimal group consists of Workers 2 and 3, with Worker 3 as the captain setting the rate at 7.0. Worker 2 gets paid 5 × 7.0 = 35 (above their minimum of 30), and Worker 3 gets paid 4 × 7.0 = 28 (exactly their minimum).

Solution Implementation

1import heapq
2from typing import List
3from math import inf
4
5class Solution:
6    def mincostToHireWorkers(
7        self, quality: List[int], wage: List[int], k: int
8    ) -> float:
9        # Create pairs of (quality, wage) and sort by wage/quality ratio
10        # Workers with lower ratio are more cost-efficient
11        workers = sorted(zip(quality, wage), key=lambda worker: worker[1] / worker[0])
12      
13        min_cost = inf
14        total_quality = 0
15        max_heap = []  # Max heap to track k workers with smallest quality
16      
17        for worker_quality, worker_wage in workers:
18            # Add current worker's quality to the total
19            total_quality += worker_quality
20            # Use negative value to simulate max heap (Python has min heap by default)
21            heapq.heappush(max_heap, -worker_quality)
22          
23            # When we have exactly k workers
24            if len(max_heap) == k:
25                # Calculate cost using current worker's wage/quality ratio
26                # All selected workers must be paid at this ratio or better
27                current_ratio = worker_wage / worker_quality
28                current_cost = current_ratio * total_quality
29                min_cost = min(min_cost, current_cost)
30              
31                # Remove worker with highest quality to make room for next iteration
32                # This keeps the k-1 workers with lowest quality
33                removed_quality = -heapq.heappop(max_heap)
34                total_quality -= removed_quality
35      
36        return min_cost
37
1class Solution {
2    public double mincostToHireWorkers(int[] quality, int[] wage, int k) {
3        int n = quality.length;
4      
5        // Create array of workers with their wage/quality ratio and quality
6        // Each worker is represented as a pair: (wage/quality ratio, quality)
7        Pair<Double, Integer>[] workers = new Pair[n];
8        for (int i = 0; i < n; i++) {
9            workers[i] = new Pair<>((double) wage[i] / quality[i], quality[i]);
10        }
11      
12        // Sort workers by their wage/quality ratio in ascending order
13        // Workers with lower ratios come first
14        Arrays.sort(workers, (a, b) -> Double.compare(a.getKey(), b.getKey()));
15      
16        // Max heap to maintain the k workers with smallest quality values
17        // We use max heap to easily remove the worker with highest quality
18        PriorityQueue<Integer> maxHeap = new PriorityQueue<>((a, b) -> b - a);
19      
20        double minCost = Double.MAX_VALUE;
21        int totalQuality = 0;
22      
23        // Iterate through workers sorted by wage/quality ratio
24        for (Pair<Double, Integer> worker : workers) {
25            double ratio = worker.getKey();
26            int workerQuality = worker.getValue();
27          
28            // Add current worker's quality to total and heap
29            totalQuality += workerQuality;
30            maxHeap.offer(workerQuality);
31          
32            // When we have exactly k workers, calculate the cost
33            if (maxHeap.size() == k) {
34                // Cost = total quality * current worker's ratio
35                // Current worker has the highest ratio among selected workers
36                minCost = Math.min(minCost, totalQuality * ratio);
37              
38                // Remove the worker with highest quality to make room for next iteration
39                totalQuality -= maxHeap.poll();
40            }
41        }
42      
43        return minCost;
44    }
45}
46
1class Solution {
2public:
3    double mincostToHireWorkers(vector<int>& quality, vector<int>& wage, int k) {
4        int n = quality.size();
5      
6        // Create pairs of (wage/quality ratio, quality) for each worker
7        // The ratio represents the minimum wage per quality unit for each worker
8        vector<pair<double, int>> workers(n);
9        for (int i = 0; i < n; ++i) {
10            workers[i] = {(double)wage[i] / quality[i], quality[i]};
11        }
12      
13        // Sort workers by their wage/quality ratio in ascending order
14        // This ensures we process workers with lower wage requirements first
15        sort(workers.begin(), workers.end());
16      
17        // Max heap to track the k workers with smallest quality values
18        // We want to minimize total quality while maintaining the wage ratio
19        priority_queue<int> maxHeap;
20      
21        double minCost = 1e18;  // Initialize with a very large number
22        int totalQuality = 0;    // Sum of qualities of current worker group
23      
24        // Iterate through workers in order of increasing wage/quality ratio
25        for (auto& [wageRatio, workerQuality] : workers) {
26            // Add current worker's quality to the group
27            totalQuality += workerQuality;
28            maxHeap.push(workerQuality);
29          
30            // When we have exactly k workers, calculate the cost
31            if (maxHeap.size() == k) {
32                // Cost = total quality * current wage ratio
33                // All workers in the group must be paid at least the current ratio
34                minCost = min(minCost, totalQuality * wageRatio);
35              
36                // Remove the worker with highest quality to make room for next iteration
37                // This keeps the k-1 workers with lowest quality
38                totalQuality -= maxHeap.top();
39                maxHeap.pop();
40            }
41        }
42      
43        return minCost;
44    }
45};
46
1function mincostToHireWorkers(quality: number[], wage: number[], k: number): number {
2    const n = quality.length;
3  
4    // Create pairs of (wage/quality ratio, quality) for each worker
5    // The ratio represents the minimum wage per quality unit for each worker
6    const workers: [number, number][] = [];
7    for (let i = 0; i < n; i++) {
8        workers.push([wage[i] / quality[i], quality[i]]);
9    }
10  
11    // Sort workers by their wage/quality ratio in ascending order
12    // This ensures we process workers with lower wage requirements first
13    workers.sort((a, b) => a[0] - b[0]);
14  
15    // Max heap to track the k workers with smallest quality values
16    // We want to minimize total quality while maintaining the wage ratio
17    // In TypeScript, we'll use an array and maintain heap property manually
18    const maxHeap: number[] = [];
19  
20    // Helper functions for max heap operations
21    const heapPush = (heap: number[], val: number): void => {
22        heap.push(val);
23        heap.sort((a, b) => b - a); // Sort in descending order for max heap
24    };
25  
26    const heapPop = (heap: number[]): number => {
27        return heap.shift()!; // Remove and return the first element (max)
28    };
29  
30    let minCost = Number.MAX_VALUE; // Initialize with a very large number
31    let totalQuality = 0; // Sum of qualities of current worker group
32  
33    // Iterate through workers in order of increasing wage/quality ratio
34    for (const [wageRatio, workerQuality] of workers) {
35        // Add current worker's quality to the group
36        totalQuality += workerQuality;
37        heapPush(maxHeap, workerQuality);
38      
39        // When we have exactly k workers, calculate the cost
40        if (maxHeap.length === k) {
41            // Cost = total quality * current wage ratio
42            // All workers in the group must be paid at least the current ratio
43            minCost = Math.min(minCost, totalQuality * wageRatio);
44          
45            // Remove the worker with highest quality to make room for next iteration
46            // This keeps the k-1 workers with lowest quality
47            totalQuality -= maxHeap[0];
48            heapPop(maxHeap);
49        }
50    }
51  
52    return minCost;
53}
54

Time and Space Complexity

Time Complexity: O(n log n) where n is the length of the quality/wage arrays.

  • Sorting the workers by their wage-to-quality ratio takes O(n log n)
  • The main loop iterates through all n workers
  • Inside the loop, each heappush operation takes O(log k) where k is the size of the heap
  • When the heap size reaches k, heappop also takes O(log k)
  • Since k ≤ n, the heap operations are bounded by O(log n)
  • Total: O(n log n) for sorting + O(n log n) for heap operations = O(n log n)

Space Complexity: O(n)

  • The sorted list t stores all n worker pairs: O(n)
  • The max heap h stores at most k elements: O(k) where k ≤ n
  • Other variables (ans, tot, q, w) use constant space: O(1)
  • Total: O(n) + O(k) = O(n) since k ≤ n

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Integer Division Instead of Float Division

A critical pitfall is using integer division when calculating the wage/quality ratio, which can lead to incorrect sorting and wrong results.

Incorrect:

workers = sorted(zip(quality, wage), key=lambda worker: worker[1] // worker[0])  # Wrong!

Correct:

workers = sorted(zip(quality, wage), key=lambda worker: worker[1] / worker[0])

2. Forgetting to Use Negative Values for Max Heap

Python's heapq module only provides a min heap. To simulate a max heap, you must push negative values. Forgetting this will keep the workers with highest quality instead of lowest.

Incorrect:

heapq.heappush(max_heap, worker_quality)  # Wrong - keeps smallest values
removed_quality = heapq.heappop(max_heap)  # Wrong - removes smallest

Correct:

heapq.heappush(max_heap, -worker_quality)  # Push negative
removed_quality = -heapq.heappop(max_heap)  # Pop and negate back

3. Updating Total Quality Incorrectly After Popping

When removing a worker from the heap, you need to subtract their quality from the total, not add the negative value returned by heappop.

Incorrect:

total_quality += heapq.heappop(max_heap)  # Wrong - adds negative value

Correct:

removed_quality = -heapq.heappop(max_heap)
total_quality -= removed_quality

4. Calculating Cost Before Having k Workers

Computing the cost when you have fewer than k workers will give invalid results. Always check that the heap size equals k.

Incorrect:

for worker_quality, worker_wage in workers:
    total_quality += worker_quality
    heapq.heappush(max_heap, -worker_quality)
    # Wrong - calculating cost regardless of group size
    current_cost = (worker_wage / worker_quality) * total_quality
    min_cost = min(min_cost, current_cost)

Correct:

if len(max_heap) == k:
    current_cost = (worker_wage / worker_quality) * total_quality
    min_cost = min(min_cost, current_cost)

5. Not Handling Edge Cases

Ensure your solution handles edge cases like when k equals n (all workers must be hired) or when k equals 1.

Solution: The current implementation handles these naturally, but always verify with test cases:

# Edge case: k = 1 (hire cheapest worker)
# Edge case: k = n (must hire all workers)
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which data structure is used to implement priority queue?


Recommended Readings

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

Load More