1.题目描述
有
n
名工人。 给定两个数组quality
和wage
,其中,quality[i]
表示第i
名工人的工作质量,其最低期望工资为wage[i]
。现在我们想雇佣
k
名工人组成一个工资组。在雇佣 一组k
名工人时,我们必须按照下述规则向他们支付工资:
- 对工资组中的每名工人,应当按其工作质量与同组其他工人的工作质量的比例来支付工资。
- 工资组中的每名工人至少应当得到他们的最低期望工资。
给定整数
k
,返回 组成满足上述条件的付费群体所需的最小金额 。在实际答案的 1 0 − 5 10^{-5} 10−5 以内的答案将被接受。
输入: quality = [10,20,5], wage = [70,50,30], k = 2
输出: 105.00000
解释: 我们向 0 号工人支付 70,向 2 号工人支付 35。
输入: quality = [3,1,10,10,1], wage = [4,8,2,2,7], k = 3
输出: 30.66667
解释: 我们向 0 号工人支付 4,向 2 号和 3 号分别支付 13.33333。
提示:
- n == quality.length == wage.length
- 1 < = k < = n < = 1 0 4 1 <= k <= n <= 10^4 1<=k<=n<=104
- 1 < = q u a l i t y [ i ] , w a g e [ i ] < = 1 0 4 1 <= quality[i], wage[i] <= 10^4 1<=quality[i],wage[i]<=104
2.思路分析
2.1 贪心+优先队列
在最优发工资方案下,至少有一名工人,发给他的工资恰好等于他的最低期望工资。
反证:假设不存在这样的工人,那么发给每名工人的工资都高于其最低期望工资,我们可以把每人的工资都下调,从而得到更优的方案,矛盾。
那么问题来了:如何找到那个发了最低期望工资的工人?
定义: r i = w a g e i q u a l i t y i r_i=\frac{wage_i}{quality_i} ri=qualityiwagei,表示 每单位工作质量的工资
若以基准
r
i
r_i
ri发工资,那么对于r值不超过
r
i
r_i
ri的工人(
r
≤
r
i
r≤r_i
r≤ri),发给他们的工资不低于最低期望工资,所以这些工人是可以随意选择的。
r
≤
r
i
=
w
a
g
e
i
q
u
a
l
i
t
y
i
=
>
w
a
g
e
i
≥
q
u
a
l
i
t
y
i
⋅
r
r \leq r_i = \frac{wage_i}{quality_i} => wage_i \geq quality_i \cdot r
r≤ri=qualityiwagei=>wagei≥qualityi⋅r
按照
r
i
r_i
ri从小到大排序即可求出这些工人
问题又来了:要选哪k位工人呢?
假设这k名工人的quality之和为sumQ,若以基准 r i r_i ri发工资,总工资为 s u m Q ⋅ r i sumQ \cdot r_i sumQ⋅ri,那么sumQ越小发的工资总额就越小。所以需要从小到大枚举 r i r_i ri,维护当前最小的k个quality值。
问题又来了:如何高效维护当前最小的k个quality值?
用一个最大堆来维护。
- 按照 r i r_i ri 从小到大的顺序遍历
- 当堆中有 k k k 个元素时,如果 quality i \textit{quality}_i qualityi比堆顶元素小,则堆顶元素可以弹出堆顶,将 q u a l i t y i {quality_i} qualityi入堆,从而得到一个更小的 sumQ,此时有可能找到一个更优解 sumQ ⋅ r i \textit{sumQ}\cdot r_i sumQ⋅ri,更新答案。
3.代码实现
class Solution:
def mincostToHireWorkers(self, quality: List[int], wage: List[int], k: int) -> float:
# 找到r值进行排序 r = wage[i]\quality[i]
qw = sorted(zip(quality, wage), key=lambda p: p[1] / p[0])
# 构建大顶堆(大小为k) 维护的元素是quality
h = [-q for q, _ in qw[:k]]
# heapify(x):将list x 转换成堆,原地,线性时间内。
heapq.heapify(h)
# 总的工作量
sumQ = -sum(h)
# 选出r值最小的k名工人组合成当前最优解
ans = sumQ * qw[k - 1][1] / qw[k - 1][0]
# 遍历r值, 更新堆
for q, w in qw[k:]:
if q < -h[0]:
# heapreplace(heap, item):弹出并返回 heap 中最小的一项,同时推入新的 item。
sumQ += heapq.heapreplace(h, -q) + q
ans = min(ans, sumQ * w / q)
return ans
复杂度分析:
- 时间复杂度:O(nlogn),其中 n 为quality 的长度。排序的时间复杂度为 O(nlogn),后面和堆有关的时间复杂度为 O(nlogk),由于 k≤n,总的时间复杂度为 O(nlogn)。
- 空间复杂度:O(n)。由于nk≤n,空间复杂度主要取决于 n。
参考:https://leetcode.cn/problems/minimum-cost-to-hire-k-workers/solution/yi-bu-bu-ti-shi-ru-he-si-kao-ci-ti-by-en-1p00/