|
| 1 | +### 题目描述 |
| 2 | + |
| 3 | +这是 LeetCode 上的 **[857. 雇佣 K 名工人的最低成本](https://leetcode.cn/problems/minimum-cost-to-hire-k-workers/solution/by-ac_oier-u42f/)** ,难度为 **困难**。 |
| 4 | + |
| 5 | +Tag : 「枚举」、「优先队列(堆)」 |
| 6 | + |
| 7 | + |
| 8 | + |
| 9 | +有 `n` 名工人。 给定两个数组 `quality` 和 `wage` ,其中,`quality[i]` 表示第 `i` 名工人的工作质量,其最低期望工资为 `wage[i]` 。 |
| 10 | + |
| 11 | +现在我们想雇佣 `k` 名工人组成一个工资组。在雇佣 一组 `k` 名工人时,我们必须按照下述规则向他们支付工资: |
| 12 | + |
| 13 | +* 对工资组中的每名工人,应当按其工作质量与同组其他工人的工作质量的比例来支付工资。 |
| 14 | +* 工资组中的每名工人至少应当得到他们的最低期望工资。 |
| 15 | + |
| 16 | +给定整数 `k`,返回 组成满足上述条件的付费群体所需的最小金额 。在实际答案的 $10^{-5}$ 以内的答案将被接受。。 |
| 17 | + |
| 18 | +示例 1: |
| 19 | +``` |
| 20 | +输入: quality = [10,20,5], wage = [70,50,30], k = 2 |
| 21 | +
|
| 22 | +输出: 105.00000 |
| 23 | +
|
| 24 | +解释: 我们向 0 号工人支付 70,向 2 号工人支付 35。 |
| 25 | +``` |
| 26 | +示例 2: |
| 27 | +``` |
| 28 | +输入: quality = [3,1,10,10,1], wage = [4,8,2,2,7], k = 3 |
| 29 | +
|
| 30 | +输出: 30.66667 |
| 31 | +
|
| 32 | +解释: 我们向 0 号工人支付 4,向 2 号和 3 号分别支付 13.33333。 |
| 33 | +``` |
| 34 | + |
| 35 | +提示: |
| 36 | +* $n = quality.length = wage.length$ |
| 37 | +* $1 <= k <= n <= 10^4$ |
| 38 | +* $1 <= quality[i], wage[i] <= 10^4$ |
| 39 | + |
| 40 | +--- |
| 41 | + |
| 42 | +### 枚举 + 优先队列(堆) |
| 43 | + |
| 44 | +为了方便,我们令 `quality` 为 `qs`,`wage` 为 `ws`。 |
| 45 | + |
| 46 | +从 $n$ 个工人中选 $k$ 个,使这 $k$ 个工人实际工资与其 $qs[i]$ 成比例,且实际工资不低于 $ws[i]$。 |
| 47 | + |
| 48 | +根据条件一,假设实际工资与能力值比值为 $t$,则所选工人的实际工资为 $qs[i] \times t$,再结合条件二可知 $qs[i] \times t >= ws[i]$,变形可得 $t >= \frac{ws[i]}{qs[i]}$。 |
| 49 | + |
| 50 | +那么在选定若干工人的情况下,为使得总支出最小,我们可以取 $t$ 为所有被选工人中的最大 $\frac{ws[i]}{qs[i]}$ 即可。 |
| 51 | + |
| 52 | +**因此最终的 $t$ 值必然是取自某个工人的实际比值 $\frac{ws[i]}{qs[i]}$,这引导我们可以通过「枚举」哪个工人的实际比值为实际 $t$ 值来做。** |
| 53 | + |
| 54 | +假设我们已经预处理出所有工人的 $\frac{ws[i]}{qs[i]}$ 比值信息,并「从小到大」进行枚举(该过程可看做是以比值信息作为维度来枚举每个工人):假设当前处理到的比值为最终使用到的 $t$,我们能够选的工人必然是在该工人的左边(根据上述分析可知,被选的工人满足 $\frac{ws[i]}{qs[i]} <= t$ 条件)。 |
| 55 | + |
| 56 | +因此,我们可以使用二维数组 `ds` 记录下每个工人的 $\frac{ws[i]}{qs[i]}$ 比值信息:$ds[i][0] = \frac{ws[i]}{qs[i]}$,$ds[i][1] = i$。并对其进行排升序,枚举每个工人的实际比值,同时动态维护枚举过程中的最小 $k$ 个 $qs[i]$(使用「大根堆」处理),并使用单变量 `tot` 记录当前堆中的 $qs[i]$ 总和,$tot \times \frac{ws[i]}{qs[i]}$ 即是以当前比值作为实际 $t$ 值时的总成本,在所有总成本中取最小值即是答案。 |
| 57 | + |
| 58 | +Java 代码: |
| 59 | +```Java |
| 60 | +class Solution { |
| 61 | + public double mincostToHireWorkers(int[] qs, int[] ws, int k) { |
| 62 | + int n = qs.length; |
| 63 | + double[][] ds = new double[n][2]; |
| 64 | + for (int i = 0; i < n; i++) { |
| 65 | + ds[i][0] = ws[i] * 1.0 / qs[i]; ds[i][1] = i * 1.0; |
| 66 | + } |
| 67 | + Arrays.sort(ds, (a,b)->Double.compare(a[0], b[0])); |
| 68 | + PriorityQueue<Integer> q = new PriorityQueue<>((a,b)->b-a); |
| 69 | + double ans = 1e18; |
| 70 | + for (int i = 0, tot = 0; i < n; i++) { |
| 71 | + int cur = qs[(int)ds[i][1]]; |
| 72 | + tot += cur; q.add(cur); |
| 73 | + if (q.size() > k) tot -= q.poll(); |
| 74 | + if (q.size() == k) ans = Math.min(ans, tot * ds[i][0]); |
| 75 | + } |
| 76 | + return ans; |
| 77 | + } |
| 78 | +} |
| 79 | +``` |
| 80 | +TypeScript 代码: |
| 81 | +```TypeScript |
| 82 | +function mincostToHireWorkers(qs: number[], ws: number[], k: number): number { |
| 83 | + const n = qs.length |
| 84 | + const ds: number[][] = new Array<Array<number>>() |
| 85 | + for (let i = 0; i < n; i++) ds.push([ws[i] / qs[i], i]) |
| 86 | + ds.sort((a,b)=>a[0]-b[0]) |
| 87 | + const q = new MaxPriorityQueue() |
| 88 | + let ans = 1e18 |
| 89 | + for (let i = 0, tot = 0; i < n; i++) { |
| 90 | + const cur = qs[ds[i][1]] |
| 91 | + tot += cur |
| 92 | + q.enqueue(cur) |
| 93 | + if (q.size() > k) tot -= q.dequeue().element |
| 94 | + if (q.size() == k) ans = Math.min(ans, tot * ds[i][0]) |
| 95 | + } |
| 96 | + return ans |
| 97 | +}; |
| 98 | +``` |
| 99 | +* 时间复杂度:构造系数并排序复杂度为 $O(n\log{n})$;枚举并计算答案复杂度为 $O(n\log{k})$。整体复杂度为 $O(n\log{n})$ |
| 100 | +* 空间复杂度:$O(n)$ |
| 101 | + |
| 102 | +--- |
| 103 | + |
| 104 | +### 最后 |
| 105 | + |
| 106 | +这是我们「刷穿 LeetCode」系列文章的第 `No.857` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。 |
| 107 | + |
| 108 | +在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。 |
| 109 | + |
| 110 | +为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。 |
| 111 | + |
| 112 | +在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。 |
| 113 | + |
0 commit comments