题目链接
2071. 你可以安排的最多任务数目 - 力扣(LeetCode)
题目描述
给你 n 个任务和 m 个工人。每个任务需要一定的力量值才能完成,需要的力量值保存在下标从 0 开始的整数数组 tasks 中,第 i 个任务需要 tasks[i] 的力量才能完成。每个工人的力量值保存在下标从 0 开始的整数数组 workers 中,第 j 个工人的力量值为 workers[j] 。每个工人只能完成 一个 任务,且力量值需要 大于等于 该任务的力量要求值(即 workers[j] >= tasks[i] )。
除此以外,你还有 pills 个神奇药丸,可以给 一个工人的力量值 增加 strength 。你可以决定给哪些工人使用药丸,但每个工人 最多 只能使用 一片 药丸。
给你下标从 0 开始的整数数组tasks 和 workers 以及两个整数 pills 和 strength ,请你返回 最多 有多少个任务可以被完成。
题目示例
示例 1 :
plain
输入:tasks = [3,2,1], workers = [0,3,3], pills = 1, strength = 1
输出:3
解释:
我们可以按照如下方案安排药丸:
- 给 0 号工人药丸。
- 0 号工人完成任务 2(0 + 1 >= 1)
- 1 号工人完成任务 1(3 >= 2)
- 2 号工人完成任务 0(3 >= 3)
示例 2 :
plain
输入:tasks = [5,4], workers = [0,0,0], pills = 1, strength = 5
输出:1
解释:
我们可以按照如下方案安排药丸:
- 给 0 号工人药丸。
- 0 号工人完成任务 0(0 + 5 >= 5)
解题思路
- 问题描述 :
- 给定两个数组
tasks和workers,分别表示任务难度和工人能力值。 - 工人可以吃药,吃药后能力值增加
strength。 - 目标是在最多使用
pills颗药的情况下,分配任务给工人,使得完成的任务数最大。
- 给定两个数组
- 关键观察 :
- 为了最大化完成任务数,应该让最强的工人完成最难的任务(贪心策略)。
- 吃药的使用应该尽量用在最需要的时候,即工人不吃药无法完成任务时。
- 可以通过二分查找来确定最多能完成的任务数。
- 算法选择 :
- 排序 :首先对
tasks和workers进行排序,方便后续处理。 - 二分查找:在可能的最大任务数范围内进行二分查找,确定最多能完成的任务数。
- 贪心策略 :在
check方法中,使用双端队列记录当前可以完成的任务,优先让工人不吃药完成最简单的任务,必须时才吃药完成最难的任务。
- 排序 :首先对
题解代码
java
class Solution {
public int maxTaskAssign(int[] tasks, int[] workers, int pills, int strength) {
// 对任务和工人数组进行排序,方便后续处理
Arrays.sort(tasks);
Arrays.sort(workers);
// 使用二分查找确定最多能完成的任务数
int left = 0; // 最小可能完成的任务数
int right = Math.min(tasks.length, workers.length) + 1; // 最大可能完成的任务数 + 1
while (left + 1 < right) {
int mid = (left + right) >>> 1; // 无符号右移,相当于 (left + right) / 2
// 检查是否能完成 mid 个任务
if (check(tasks, workers, pills, strength, mid)) {
left = mid; // 可以完成 mid 个任务,尝试更大的值
} else {
right = mid; // 不能完成 mid 个任务,尝试更小的值
}
}
return left; // 返回最大能完成的任务数
}
private boolean check(int[] tasks, int[] workers, int pills, int strength, int k) {
// 使用双端队列记录当前可以完成的任务
Deque<Integer> validTasks = new ArrayDeque<>();
int i = 0; // 指向 tasks 的指针
// 遍历最强的 k 个工人
for (int j = workers.length - k; j < workers.length; j++) {
int w = workers[j]; // 当前工人的能力值
// 将当前工人吃药后能完成的任务加入队列
while (i < k && tasks[i] <= w + strength) {
validTasks.addLast(tasks[i]);
i++;
}
// 如果没有任务可以完成,直接返回 false
if (validTasks.isEmpty()) {
return false;
}
// 如果当前工人不吃药就能完成最简单的任务
if (w >= validTasks.peekFirst()) {
validTasks.pollFirst(); // 完成最简单的任务
continue;
}
// 必须吃药才能完成任务
if (pills == 0) { // 没有药了,无法完成任务
return false;
}
pills--; // 消耗一颗药
validTasks.pollLast(); // 完成最难的任务(贪心策略)
}
return true; // 所有工人都能完成任务
}
}
复杂度分析
- 时间复杂度 :
- 排序 :
Arrays.sort的时间复杂度为O(n log n)和O(m log m),其中n和m分别是tasks和workers的长度。 - 二分查找 :最多进行
O(log(min(n, m)))次查找。 - 每次 ** **
**check**:O(k),其中k是当前尝试的任务数。 - 总时间复杂度 :
O((n log n) + (m log m) + (min(n, m) * log(min(n, m)))),可以简化为O((n + m) log (n + m))。
- 排序 :
- 空间复杂度 :
- 排序 :
Arrays.sort使用O(log n)或O(log m)的栈空间(取决于具体实现)。 - 双端队列 :最多存储
O(k)个元素,其中k是最大可能完成的任务数。 - 总空间复杂度 :
O(min(n, m))。
- 排序 :