Leetcode 857. 雇佣 K 名工人的最低成本

1.题目描述

n 名工人。 给定两个数组 qualitywage ,其中,quality[i] 表示第 i 名工人的工作质量,其最低期望工资为 wage[i]

现在我们想雇佣 k 名工人组成一个工资组。在雇佣 一组 k 名工人时,我们必须按照下述规则向他们支付工资:

  • 对工资组中的每名工人,应当按其工作质量与同组其他工人的工作质量的比例来支付工资。
  • 工资组中的每名工人至少应当得到他们的最低期望工资。

给定整数 k ,返回 组成满足上述条件的付费群体所需的最小金额 。在实际答案的 1 0 − 5 10^{-5} 105 以内的答案将被接受。


输入: 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 rri),发给他们的工资不低于最低期望工资,所以这些工人是可以随意选择的。
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 rri=qualityiwagei=>wageiqualityir
按照 r i r_i ri从小到大排序即可求出这些工人

问题又来了:要选哪k位工人呢?

假设这k名工人的quality之和为sumQ,若以基准 r i r_i ri发工资,总工资为 s u m Q ⋅ r i sumQ \cdot r_i sumQri,那么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 sumQri,更新答案。

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/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值