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
        //初始化poll
        int 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
        //初始化poll
        int 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 = 0right = 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 个任务 → 返回 trueleft = 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 = 0validTasks = [5]

第 2 位工人:w = 4

  • 能完成任务 5
    validTasks = []

第 3 位工人:w = 6

  • 继续推进 itasks[2] = 8, tasks[3] = 9 都能入队(能力+药=11)
    validTasks = [8,9]

  • 能力不够完成 8,但没药了 ❌ → 失败

返回 false → right = 4

尝试 mid = 3 已知成功

尝试 mid = 4 失败

最终 left = 3,即最多 完成 3 个任务

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值