A clear explanation of hiring exactly k workers with minimum total cost using wage-to-quality ratios, sorting, and a max heap.
Problem Restatement
We are given n workers.
Each worker has:
| Data | Meaning |
|---|---|
quality[i] | The quality value of worker i |
wage[i] | The minimum wage worker i must receive |
We need to hire exactly k workers.
The payment must satisfy two rules:
- Every hired worker must be paid at least their minimum wage.
- In the hired group, pay must be proportional to quality.
That means if one worker has twice the quality of another worker, they must receive twice the pay.
Return the minimum total amount of money needed to hire exactly k workers.
Answers within 10^-5 of the correct answer are accepted.
Input and Output
| Item | Meaning |
|---|---|
| Input | quality, a list of worker quality values |
| Input | wage, a list of minimum wage expectations |
| Input | k, the number of workers to hire |
| Output | The minimum total cost as a float |
Function shape:
class Solution:
def mincostToHireWorkers(self, quality: list[int], wage: list[int], k: int) -> float:
...Examples
Example 1:
quality = [10, 20, 5]
wage = [70, 50, 30]
k = 2Worker ratios:
| Worker | Quality | Wage | Wage per quality |
|---|---|---|---|
0 | 10 | 70 | 7.0 |
1 | 20 | 50 | 2.5 |
2 | 5 | 30 | 6.0 |
If we hire workers 0 and 2, the highest wage-to-quality ratio is 7.0.
So everyone must be paid at rate 7.0 per quality.
| Worker | Quality | Pay |
|---|---|---|
0 | 10 | 70 |
2 | 5 | 35 |
Total cost:
70 + 35 = 105So the answer is:
105.0Example 2:
quality = [3, 1, 10, 10, 1]
wage = [4, 8, 2, 2, 7]
k = 3The minimum cost is:
30.66667First Thought: Try Every Group
A direct approach is to try every group of exactly k workers.
For each group:
- Find the highest wage-to-quality ratio in that group.
- Use that ratio as the pay rate.
- Compute total cost as:
rate * sum_of_quality- Keep the minimum total cost.
This is correct, but it is too slow because the number of possible groups can be huge.
We need a way to avoid checking every combination.
Key Insight
Because pay must be proportional to quality, every hired worker is paid using the same rate:
pay = quality * rateFor worker i to accept, we need:
quality[i] * rate >= wage[i]So:
rate >= wage[i] / quality[i]For a chosen group, the rate must be at least the largest ratio among that group.
So every group has a controlling worker: the worker with the highest wage / quality ratio.
If we fix that controlling ratio, the total cost becomes:
ratio * total_qualityFor a fixed ratio, minimizing cost means choosing the k workers with the smallest total quality among workers whose ratio is not greater than that fixed ratio.
This gives us the strategy:
- Sort workers by
wage / quality. - Process workers from smallest ratio to largest ratio.
- Treat the current worker as the highest-ratio worker in the group.
- Among all workers seen so far, keep the
ksmallest qualities. - Compute the cost when we have exactly
kworkers.
Algorithm
Build a list of workers:
(ratio, quality)where:
ratio = wage[i] / quality[i]Sort workers by ratio.
Maintain:
quality_sumas the sum of selected qualities.
Use a max heap to keep the k smallest qualities seen so far.
Python only has a min heap, so we store negative qualities.
For each worker in sorted order:
- Add the worker’s quality to the heap.
- Add the quality to
quality_sum. - If the heap has more than
kworkers, remove the largest quality. - If the heap has exactly
kworkers, compute:
quality_sum * ratio- Keep the minimum cost.
Correctness
Sort workers by increasing wage-to-quality ratio.
When processing a worker with ratio r, all previously seen workers have ratio at most r.
Therefore, any group formed from the current worker and previously seen workers can be paid at rate r, because every worker in that group has minimum required ratio at most r.
For this fixed rate r, the total cost is:
r * total_qualitySince r is fixed, the cheapest valid group is the group with the smallest total quality among the seen workers.
The max heap maintains exactly that: the k smallest qualities among workers seen so far. If more than k workers are available, the largest quality is removed because keeping it can only increase the total cost for the same rate.
Every optimal group has some worker with the highest wage-to-quality ratio. When the algorithm reaches that worker in sorted order, all workers in the optimal group have already been seen. The heap considers the best possible k qualities among those workers, so the cost computed at that point is no worse than the optimal group using that highest ratio.
Taking the minimum over all such steps gives the global minimum cost.
Complexity
Let:
n = len(quality)| Metric | Value | Why |
|---|---|---|
| Time | O(n log n) | Sorting workers costs O(n log n), and each heap operation costs O(log k) |
| Space | O(n) | The sorted worker list stores n workers |
The heap stores at most k qualities.
Implementation
import heapq
class Solution:
def mincostToHireWorkers(self, quality: list[int], wage: list[int], k: int) -> float:
workers = []
for q, w in zip(quality, wage):
ratio = w / q
workers.append((ratio, q))
workers.sort()
max_heap = []
quality_sum = 0
answer = float("inf")
for ratio, q in workers:
heapq.heappush(max_heap, -q)
quality_sum += q
if len(max_heap) > k:
removed_quality = -heapq.heappop(max_heap)
quality_sum -= removed_quality
if len(max_heap) == k:
answer = min(answer, quality_sum * ratio)
return answerCode Explanation
We first convert each worker into a ratio-quality pair:
for q, w in zip(quality, wage):
ratio = w / q
workers.append((ratio, q))The ratio means the minimum pay rate needed by that worker.
Then we sort:
workers.sort()Now we process possible controlling workers from lowest ratio to highest ratio.
We use a heap:
max_heap = []Python’s heapq is a min heap. To simulate a max heap, we store negative qualities.
When adding a worker:
heapq.heappush(max_heap, -q)
quality_sum += qIf there are more than k workers, we remove the largest quality:
removed_quality = -heapq.heappop(max_heap)
quality_sum -= removed_qualityThis keeps the selected total quality as small as possible.
When exactly k workers are selected, the current ratio can pay all of them:
answer = min(answer, quality_sum * ratio)Finally, we return the minimum cost:
return answerTesting
def test_min_cost_to_hire_workers():
s = Solution()
assert abs(s.mincostToHireWorkers([10, 20, 5], [70, 50, 30], 2) - 105.0) < 1e-5
assert abs(
s.mincostToHireWorkers(
[3, 1, 10, 10, 1],
[4, 8, 2, 2, 7],
3,
) - 30.6666666667
) < 1e-5
assert abs(s.mincostToHireWorkers([1], [5], 1) - 5.0) < 1e-5
assert abs(s.mincostToHireWorkers([5, 10], [10, 20], 2) - 30.0) < 1e-5
assert abs(s.mincostToHireWorkers([4, 8, 2], [8, 16, 10], 2) - 20.0) < 1e-5
print("all tests passed")
test_min_cost_to_hire_workers()Test meaning:
| Test | Why |
|---|---|
[10, 20, 5], [70, 50, 30], k = 2 | Standard example |
| Larger official-style case | Checks fractional answer |
| One worker | Minimum group size |
| Same ratio workers | Checks proportional payment |
| High-ratio low-quality worker | Checks greedy choice with heap |