写在前面:
今天来看一道比较困难的题目, Minimum Cost to Hire K Workers。这道较新的 Leetcode Hard 的题目是我迄今为止分享过的最难的一道题,对题目的理解,关系的归纳,以及对数据结构的运用都有很高的要求。对于答题者的数学思维也有不低的要求和考量,如果不能正确运用数学推演简化题目其中的关系的话,解开这道题(或者做出 AC 的答案) 不太容易
同时,这道题目的背景和当下打工人的困境一拍即合。这道题目的本质就是用最少的钱招足够的工产生最多的价值 -- 妥妥就是一个降本增效的绩效考核算法呀这不是~~ 作为被优化最频繁的岗位,咱程序员码农可真是做到自己的人生题了哈哈哈~~
不仅仅是这道题目有趣的背景,他的价值同样不菲,在最近三个月内连续出现在 Google 的面试真题当中,近半年内也在 Amazon, Meta 等多个大厂的面试题目中出现。可以看出这道题目在考察学生综合能力的层面非常全面。那让我们也跟随这篇文章一起来探索一下他的解法吧!
题目介绍:
题目信息:
- 题目链接:https://leetcode.com/problems/minimum-cost-to-hire-k-workers/description/
- 题目类型:Array,PriorityQueue, Math
- 题目难度:Hard (非常难想!)
题目问题:
- 给定两个长度相等的数组, 每个位置代表同一个 worker 的 quality 和 wage,一一对应:
- Quality: [10, 20, 5]
- Wage: [70, 50, 30]
- 给定一个整数 K,代表我们需要雇佣的 worker 个数,满足条件的雇佣组被称为 paid group
- 同一个 paid group 的限定条件:
- 在同一个雇佣组中,每一个工人都必须至少获得他们所期望的最小工资(也就是 wage 数组中对应每一个 worker 位置的工资)
- 在同一个雇佣组中,每一个工人的工资必须和他们的劳动 quality 成正比,比如,如果雇员 A 的 Quality 是 雇员 B 的两倍,则雇员 A 的工资,在同时满足条件1的情况下,必须要是雇员 B 的两倍
- 求出在所有满足条件的 paid group 中,花销最少的那个组,返回最小需要的工资
题目想法:
这道题乍一看非常难,因为有两个限定条件,就涉及不仅要选工资最便宜的人进行工作,同时还得考虑可能工资低的人可能 Quality 会更高,这样导致低期望工资的选择不一定带来低的成本
Wage 与 Quality 比率:
既然总的雇佣开销同时受 Quality 和 Wage 的影响,我们何不计算出每一位工人的单位 Quality 所需工资呢,这样的话,我们只要 minimize 这个比率,就可以同时做到考虑低工资的同时考虑低quality。更小的单位 Quality 工资 意味着更少的 综合成本(怎么听着那么耳熟,这不就是降本增效吗????)
总 Quality:
我们不仅要从 “降本增效” 的角度请单价最便宜的工人,同时,因为我们整组的工资的最小值一定是工资最高 (也就是 Wage Quality 比率最高)的那一位工人的(满足条件1),所以更小的 quality 也是我们降低总成本的一个考量维度。
结合在一起:
将两个考量指标结合在一起以后,我们就可以得出,我们需要同时在 wage Quality 和 总 Quality 纬度上取最小即可:
- wage Quality Ratio 取最小,可以先遍历数组进行 ratio 的计算,然后将所有的 ratio 进行排序
- 在选择最小的 ratios 的时候,同时将选择的 quality 进行排序,当我们有超过 k 人的时候,总是删除 quality 最多的那个人,并计算判断是否获得了最小的情况
- 我们可以使用 Max heap 对 quality 进行轻松的排序
Tips:
如果觉得我讲的不够清楚的话,也可以上 Leetcode 官方解析查看(但好像要会员),地址在:
https://leetcode.com/problems/minimum-cost-to-hire-k-workers/editorial/
题目解法:
- 定义 Maxheap,WageToQuality Ratio 向量
- 遍历所有 wage 和 quality 内元素:
- wageToQuality[i] = pair{double(wage[i]) / quality[i], quality[i]}
- sort WageToQualityRatio vector
- 遍历 WageToQuality vector:
- Maxheap.push(WageToQuality[i].second)
- totalQuality += WageToQuality[i].second
- 如果 Maxheap 长度超过 k:
- totalQuality 减去 Maxheap 最上面数
- Maxheap.pop()
- 如果 Maxheap 长度 = k:
- res = min(res, totalQuality * WageToQuality[i][0])
- 返回 res 结果
题目代码:
class Solution {
public:
double mincostToHireWorkers(vector<int>& quality, vector<int>& wage, int k) {
vector<pair<double, int>> wageToQualityRatio;
int workerNum = quality.size();
double minRes = numeric_limits<double>::max();
// retrieve the work wage ratio for each worker
for(int i = 0; i < workerNum; i++){
wageToQualityRatio.push_back({static_cast<double>(wage[i])/quality[i], quality[i]});
}
// this ensures that when we iterate from left to right, the current index's wage quality ratio will satisfy every worker's wages expectation on the left
sort(wageToQualityRatio.begin(), wageToQualityRatio.end());
//define a maxheap for quality comparison
priority_queue<int> workerQuality;
int totalQuality = 0;
for(int i = 0; i < wageToQualityRatio.size(); i++){
workerQuality.push(wageToQualityRatio[i].second);
totalQuality += wageToQualityRatio[i].second;
// too many workers, pop the worker with the most quality since the wage quality ratio is fixed at each step
if(workerQuality.size() > k){
totalQuality -= workerQuality.top();
workerQuality.pop();
}
// whenever we have enough worker, calculate and see if it is minimum
if(workerQuality.size() == k){
minRes = min(minRes, wageToQualityRatio[i].first * totalQuality);
}
}
return minRes;
}
};
- Runtime: O(nlogn) + O(nlogk)
- 排序 wageQualityRatio:O(nlogn)
- 遍历,并在每一个位置添加删改 Maxheap:O(nlogk)
- Space: O(n) + O(k)
- Tips:对于最大的 double:没有 DOUBLE_MAX, 可以使用:numeric_limits<double>::max()