LeetCode 621.任务调度器

209 阅读3分钟

Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情

题目:给定一个字符数组tasks表示需要执行的任务列表,每个字母代表不同任务。所有任务可以顺序执行并且运行时间都相同,但两个相同的任务存在长度为n的冷却期,要求计算完成所有任务所需消耗的最短时间。

例如:

输入:tasks = ["A","A","A","A","A","A","B","C","D","E","F","G"], n = 2
输出:16
解释:一种可能的解决方案是:
     A -> B -> C -> A -> D -> E -> A -> F -> G -> A -> (待命) -> (待命) -> A -> (待命) -> (待命) -> A

解题思路

根据题目意思手动模拟得到答案很简单,例如对于输入["A","A","A","B","B","B"]n=2

手动模拟为先统计每个任务的数量,得到A:3, B:3,之后根据数量进行排序,数量大的排前面,首先输出任务A,更新A的数量,再往后选b,而n=2,但此时集合已经没有任务了,因此必须等待,时间可直接加n+1。之后一直循环,直到所有任务的数量为0

思路并不难,但在代码中实现却存在困难,统计数量可以使用map来统计,之后需要将map进行排序,可行的方式是将map存入list,之后调用list.sort()自己定义排序规则进行排序,这样有序的任务序列就得到了,但每次删除任务之后还需要将任务再次进行排序,这样肯定不行。

此时就希望找到一种数据结构,这个数据结构能在每次新增任务都自动进行排序,优先级队列就进入考虑,我们可以每次删除堆顶的任务,更新任务数量,判断数量是否等于0,不等于则代表此任务没结束需要再次执行,此时使用list将任务存起来,而相同任务间隔则很好解决,根据n进行弹出堆元素,如果堆空了,则直接增加时间为n+1即可,可得代码如下:

public int leastInterval(char[] tasks, int n) {
    HashMap<Integer, Integer> map = new HashMap<>();
    for(Character c:tasks){
        map.put(c-65, map.getOrDefault(c-65, 0) + 1);
    }
    PriorityQueue<int[]> queue = new PriorityQueue<>((t1, t2) -> t2[1] - t1[1]);
    for(Map.Entry<Integer, Integer> entry:map.entrySet()){
        int key = entry.getKey();
        int value = entry.getValue();
        queue.offer(new int[]{key, value});
    }
    int res = 0;
    int k = 0;
    ArrayList<int[]> tempList = new ArrayList<>();
    while (!queue.isEmpty()){
        tempList.clear();
        k = n+1;
        while(!queue.isEmpty()&&k>0){
            int[] poll = queue.poll();
            poll[1]--;
            if(poll[1]!=0) tempList.add(poll);
            k--;
        }
        queue.addAll(tempList);
        res += n+1;
    }
    return res-k;
}

代码的最后减k在这里解释一下:

因为最后一次可能出现栈空的情况,例如最终只剩下了一个任务A,此时时间只有1,但最后却加了n+1的时间,而实际时间应该是n+1-k,因此最后要减k

最终耗时28ms,超越16%的Java用户。

题解还有一种黑科技有点难想,下面只贴上代码:

public int leastInterval2(char[] tasks, int n) {
    int[] count = new int[26];
    for(Character c:tasks){
        count[c-65] ++;
    }
    Arrays.sort(count);//词频排序,升序排序,count[25]是频率最高的
    int maxCount = 0;
    //统计有多少个频率最高的字母
    for (int i = 25; i >= 0; i--) {
        if(count[i] != count[25]){
            break;
        }
        maxCount++;
    }
    //公式算出的值可能会比数组的长度小,取两者中最大的那个
    return Math.max((count[25] - 1) * (n + 1) + maxCount , tasks.length);
}