【Leetcode】621. Task Scheduler(配数学证明)

题目地址:

https://leetcode.com/problems/task-scheduler/

给定一系列任务,以大写字母表示,再给定一个冷却时间,每个任务需要花一个单位的时间完成,并且相同任务之间需要 n n n个单位的冷却时间。要求安排这些任务的执行顺序,使得花费的总时间最小。

直觉上来讲,这个总时间是由出现次数最多的任务决定的。我们可以这样以贪心的方式安排任务:
1、先将每个任务按出现次数由大到小排序,然后依次执行任务,并累加任务执行的时间;
2、如果这样一轮依次执行任务的总时间是小于 n + 1 n+1 n+1的,并且任务还没全执行完,那么说明需要idle,累加一下idle的时间;如果这样一轮依次执行任务的总时间是大于等于 n + 1 n+1 n+1的,那说明这一轮任务顺利执行了,不需要idle;
3、把执行完一轮的任务的出现次数减去 1 1 1,然后再重复上述 1 1 1 2 2 2的操作。

首先证明一下上述算法得到总时间最少。数学归纳法,对任务数进行归纳。当任务数为 1 1 1的时候显然正确。假设当任务数小于 n n n的时候成立,当任务数等于 n n n的时候,先按照上面贪心的方法得到一个安排方案,接着去掉最后的任务,如果最后的任务之前不是idle,由于之前的 n − 1 n-1 n1个任务的方案是按照上面的贪心法得到的,由归纳假设,那是能得到的耗时最少的方案,所以加上最后一个任务当然也是耗时最少的方案(如果不然,删掉最后一个任务就得到了更优解,与归纳假设矛盾),所以结论成立;如果最后的任务之前是idle,那就说明最后一个任务恰好是全局出现次数最多的任务,设为任务 X X X,由于有idle的限制,所花费的最少时间的方案也必须大于等于 X , . . . , X , . . . , X , . . . , X X,...,X,...,X,...,X X,...,X,...,X,...,X这样的方案的花费时间(否则由鸽笼原理,就会有俩 X X X在idle的时间里产生冲突),而贪心法得到的时间花费恰好就是这个最小值,所以结论也成立。综上,算法正确。

其次考虑如何实现。(除了以下方法之外,还有一种偏数学的办法,参考https://blog.csdn.net/qq_46105170/article/details/109113420)。

法1:堆。由于每次都要找出出现次数最高的任务,所以可以考虑用堆来做。先将每个任务出现了多少次统计一下,然后开一个大顶堆,出现次数多的任务优先出堆,每一轮出堆完成之后,看一下是否需要idle,需要idle的话再累加一下idle的时间。代码如下:

import java.util.ArrayList;
import java.util.List;
import java.util.PriorityQueue;

public class Solution {
    public int leastInterval(char[] tasks, int n) {
        if (n == 0) {
            return tasks.length;
        }
        
        int[] count = new int[26];
        for (int i = 0; i < tasks.length; i++) {
            count[tasks[i] - 'A']++;
        }
        
        PriorityQueue<Character> maxHeap = new PriorityQueue<>((c1, c2) -> -Integer.compare(count[c1 - 'A'], count[c2 - 'A']));
        for (int i = 0; i < count.length; i++) {
            if (count[i] > 0) {
                maxHeap.add((char) ('A' + i));
            }
        }
        
        int res = 0;
        while (!maxHeap.isEmpty()) {
        	// 记录一下哪些任务还有剩余,方便后面重新入堆
            List<Character> list = new ArrayList<>();
            int size = maxHeap.size();
            for (int i = 0; i < Math.min(size, n + 1); i++) {
                char top = maxHeap.poll();
                // process掉一个任务
                count[top - 'A']--;
                // 如果该任务还有剩余,则记录之
                if (count[top - 'A'] > 0) {
                    list.add(top);
                }
                
                // 累加process的时间
                res++;
            }
            
            // 将还没process完的任务重新入堆
            for (char ch : list) {
                maxHeap.offer(ch);
            }
            
            // 如果后面还有任务需要process,并且上一轮出堆的任务需要冷却,则累加idle的时间
            if (!maxHeap.isEmpty() && size < n + 1) {
                res += n + 1 - size;
            }
        }
        
        return res;
    }
}

时间复杂度 O ( t ) O(t) O(t),其中 t t t指的是最终答案,空间 O ( 1 ) O(1) O(1)

法2:直接计算idle时间。不妨设任务 A A A出现次数最多,那么由上面的贪心算法,任务安排方式应该是形如 A , . . . , A , . . . , A , . . . A,...,A,...,A,... A,...,A,...,A,...这样,我们先把 A A A后面idle的时间的”空位“数算出来,注意,最后一个 A A A的后面是不必idle的,所以空位数应该是 A A A的数量减去 1 1 1,再乘以 n n n。接着就开始按照贪心算法里的流程填数了。假设 B B B是出现次数第二多的任务(这里允许出现次数和 A A A一样多),想象把 B B B分配到 A A A的后面,如果 B B B出现次数少于 A A A,那么idle的空位就少了 B B B的次数这么多,否则的话,就少了 A A A次数减去 1 1 1这么多。把所有任务都插入进去之后,看一下还剩余的空位的个数,如果此时还剩余负数个空位,说明中间没有idle,置idle为 0 0 0,直接返回任务数;否则返回idle值加上任务数。代码如下:

import java.util.Arrays;

public class Solution {
    public int leastInterval(char[] tasks, int n) {
        int[] count = new int[26];
        for (int i = 0; i < tasks.length; i++) {
            count[tasks[i] - 'A']++;
        }
    
    	// 按count从大到小排序
        Arrays.sort(count);
        reverse(count);
        
        int idle = (count[0] - 1) * n;
        for (int i = 1; i < count.length; i++) {
            idle -= Math.min(count[0] - 1, count[i]);
        }
        
        idle = Math.max(0, idle);
        return idle + tasks.length;
    }
    
    private void reverse(int[] nums) {
        for (int i = 0, j = nums.length - 1; i < j; i++, j--) {
            int tmp = nums[i];
            nums[i] = nums[j];
            nums[j] = tmp;
        }
    }
}

时间复杂度 O ( m ) O(m) O(m) m m m是任务个数。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值