LeetCode 2071 你可以安排的最多任务数目 题解(附带自己的错误做题思路 过了25/49)
示例
输入: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)
对于本题我的大致思想是通过先对两个数组进行排序,然后对原本数组的tasks和workers进行遍历,让workers中能完成相对于tasks数组中的他能完成的最大值任务数,并将其下标记录入队列,在下次当工人们嗑药后跳过这几个已经由最大值工人们完成的tasks。
光说还是太抽象了,我会大致画一个图来完成我的思想展现。
先通过sort排序两个数组,根据原始数组,从后往前遍历数组tasks,匹配workers数组,用workers数组中的最大值去干掉tasks中能干掉的最大值并将下标记录下来保存至队列queue,在此过程中我们也要记录下使用了workers多少数量的元素,即以下部分代码
sum用来统计原数组可以最大程度解决几个任务
Arrays.sort(tasks);Arrays.sort(workers);int n = tasks.length;int m = workers.length;int l=0;int sum = 0;int size = 0;Queue<Integer> queue = new LinkedList<>();for(int i=n-1;i>=0&&m-l-1>=0;i--){if(workers[m-l-1]>=tasks[i]){queue.offer(i);System.out.println("i:"+i);System.out.println("Queue elements: " + queue);sum++;l++;size++;}}
之后将栈反转,即使得[1,0]
变为[0,1]
// 使用栈反转队列顺序Stack<Integer> stack = new Stack<>();while (!queue.isEmpty()) {stack.push(queue.poll());}// 将栈中的元素重新放回队列while (!stack.isEmpty()) {queue.offer(stack.pop());}
此时我们给所有工人喂药去匹配任务(你不做,有的是人做😭)
for(int i=0;i<m;i++){workers[i] = workers[i]+strength;}
然后重新给l和r赋值为0,l
决定tasks的遍历程度,r
决定workers的遍历程度,如果队列存在,将队列先抛出一个值初始化,进入循环r<m-size&&l<n
,是由于workers
中已经用掉的不能在使用了,所以减去后面匹配过的长度的子数组,l的话我们就可以通过队列进行筛选,筛选直至队列为空,然后通过判断workers[r]>=tasks[l]&&num>0
对药的数量减减,对总的匹配任务加加,并将tasks
的边界l
右移,且只要是未标记在队列中的值经历过一次这种判断都得使得workers
的边界r
右移,为了寻找能判断成功的数
int num = pills;l=0;int r=0;//相等poll,不相等不poll//初始化pollint que = -1;if(!queue.isEmpty()){que = queue.poll();}while(r<m-size&&l<n){if(que==l){if(!queue.isEmpty()){que = queue.poll();}l++;continue;}if(workers[r]>=tasks[l]&&num>0){num--;System.out.println("num:"+num);sum++;System.out.println("sum:"+sum);l++;System.out.println("l:"+l);}r++;}
总的代码如下
class Solution {public int maxTaskAssign(int[] tasks, int[] workers, int pills, int strength) {Arrays.sort(tasks);Arrays.sort(workers);int n = tasks.length;int m = workers.length;int l=0;int sum = 0;int size = 0;Queue<Integer> queue = new LinkedList<>();for(int i=n-1;i>=0&&m-l-1>=0;i--){if(workers[m-l-1]>=tasks[i]){queue.offer(i);sum++;l++;size++;}}// 使用栈反转队列顺序Stack<Integer> stack = new Stack<>();while (!queue.isEmpty()) {stack.push(queue.poll());}// 将栈中的元素重新放回队列while (!stack.isEmpty()) {queue.offer(stack.pop());}for(int i=0;i<m;i++){workers[i] = workers[i]+strength;}int num = pills;l=0;int r=0;//相等poll,不相等不poll//初始化pollint que = -1;if(!queue.isEmpty()){que = queue.poll();}while(r<m-size&&l<n){if(que==l){if(!queue.isEmpty()){que = queue.poll();}l++;continue;}if(workers[r]>=tasks[l]&&num>0){num--;sum++;l++;}r++;}return sum;}
}
以上是我的错误思路,错误的原因在于我是用最大的工人去将能完成的最大任务给完成了,却忽略了当药足够多的时候,最大的工人还能完成更大强度的工作,是结果达到最优,所以我们不能将本题分支开来写,必须一一统计其能完成的最大值。
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;while (left + 1 < right) {int mid = (left + right) >>> 1;if (check(tasks, workers, pills, strength, mid)) {left = mid;} else {right = mid;}}return left;}private boolean check(int[] tasks, int[] workers, int pills, int strength, int k) {// 贪心:用最强的 k 名工人,完成最简单的 k 个任务Deque<Integer> validTasks = new ArrayDeque<>();int i = 0;for (int j = workers.length - k; j < workers.length; j++) { // 枚举工人int w = workers[j];// 在吃药的情况下,把能完成的任务记录到 validTasks 中while (i < k && tasks[i] <= w + strength) {validTasks.addLast(tasks[i]);i++;}// 即使吃药也无法完成任务if (validTasks.isEmpty()) {return false;}// 无需吃药就能完成(最简单的)任务if (w >= validTasks.peekFirst()) {validTasks.pollFirst();continue;}// 必须吃药if (pills == 0) { // 没药了return false;}pills--;// 完成(能完成的)最难的任务validTasks.pollLast();}return true;}
}
该题解的思路是通过二分,然后一一枚举,找出最大的几个工人和最小的几个任务,然后从最小的工人开始嗑药去匹配,看能匹配到最大的任务是哪个,如果存在不需要吃药就能匹配到的,就直接将该任务抛出,然后继续下一个,并且没有消耗药,如果不存在就说明必须吃药,那就最大程度的利用吃药的这个工人将最大能抛出的值抛出,全部执行完还没return就说明是可以完成当前数量的任务,再增加任务量进行验证可行性,反之减少再验证。
tasks = [5, 9, 8, 5, 9]
workers = [1, 6, 4, 2, 6]
pills = 1
strength = 5
排序
tasks = [5, 5, 8, 9, 9]
workers = [1, 2, 4, 6, 6]
Step 2: 二分查找答案
我们要找出 最多能完成多少个任务。
left = 0
,right = min(5,5)+1 = 6
我们开始二分:
尝试 mid = 3
check(tasks, workers, pills=1, strength=5, k=3)
-
取最强的 3 个工人:
[4, 6, 6]
-
取最简单的 3 个任务:
[5, 5, 8]
初始化:
-
validTasks = []
-
i = 0
第 1 位工人:w = 4
-
能力 + 药 =
4 + 5 = 9
-
所以
tasks[0] = 5
,tasks[1] = 5
,tasks[2] = 8
都可加入:
→validTasks = [5, 5, 8]
-
w = 4
不能直接完成5
-
所以吃药,药数变为
0
,完成最难任务8
→validTasks = [5, 5]
第 2 位工人:w = 6
- 直接可以完成
5
,不用吃药
→validTasks = [5]
第 3 位工人:w = 6
- 直接完成
5
→validTasks = []
✅ 成功完成 3 个任务 → 返回 true
,left = 3
尝试 mid = 4
check(tasks, workers, pills=1, strength=5, k=4)
-
工人:[2, 4, 6, 6]
-
任务:[5, 5, 8, 9]
初始化:
-
validTasks = []
-
i = 0
第 1 位工人:w = 2
-
能力+药=7,可以加入:5,5(8太大)
→validTasks = [5,5]
-
不能完成任何任务,只能吃药:完成最难的
5
→pills = 0
,validTasks = [5]
第 2 位工人:w = 4
- 能完成任务
5
→validTasks = []
第 3 位工人:w = 6
-
继续推进
i
,tasks[2] = 8
,tasks[3] = 9
都能入队(能力+药=11)
→validTasks = [8,9]
-
能力不够完成
8
,但没药了 ❌ → 失败
返回 false → right = 4
尝试 mid = 3
已知成功
尝试 mid = 4
失败
最终 left = 3
,即最多 完成 3 个任务