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:
- Each hired worker must receive at least their minimum wage expectation
- 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.
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:
- Sort workers by their
wage/quality
ratio - Consider each worker as a potential "captain" (the one setting the rate)
- 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) - 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:
-
Sort workers by wage/quality ratio: We create tuples of
(quality, wage)
and sort them bywage/quality
ratio using a lambda functionlambda x: x[1] / x[0]
. This ensures workers are ordered from lowest to highest ratio. -
Initialize variables:
ans = inf
: Tracks the minimum cost found so fartot = 0
: Maintains the sum of qualities in our current grouph = []
: A max heap (using negative values in Python's min heap) to store qualities
-
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)
- Add their quality to the running total:
-
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
- Calculate cost with current worker as captain:
-
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 EvaluatorExample 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 takesO(log k)
wherek
is the size of the heap - When the heap size reaches
k
,heappop
also takesO(log k)
- Since
k ≤ n
, the heap operations are bounded byO(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 alln
worker pairs:O(n)
- The max heap
h
stores at mostk
elements:O(k)
wherek ≤ n
- Other variables (
ans
,tot
,q
,w
) use constant space:O(1)
- Total:
O(n) + O(k) = O(n)
sincek ≤ 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)
Which data structure is used to implement priority queue?
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
https assets algo monster cover_photos heap svg Priority Queue and Heap What is the relationship between priority queue and heap Priority Queue is an Abstract Data Type and Heap is the concrete data structure we use to implement a priority queue Priority Queue A priority queue is a data structure
Want a Structured Path to Master System Design Too? Don’t Miss This!