LeetCode——任务调度器

1. 任务调度器

这道题一上手会犯直接找模拟方法,然后根据模拟方法来得出结果。也不是说直接找模拟方法不对,只是说一开始没有更深入的思考的话,模拟方法很可能是错的,导致浪费时间,像这种题,要注意其中的极限思想,比如这道题,假如其他变量不动,把等待间隔不断调大会发生什么?然后出现变化的分界点是什么?按照这样思考,就不难出结果了。

题目链接

1.1. 问题

给你一个用字符数组 tasks 表示的 CPU 需要执行的任务列表。其中每个字母表示一种不同种类的任务。任务可以以任意顺序执行,并且每个任务都可以在 1 个单位时间内执行完。在任何一个单位时间,CPU 可以完成一个任务,或者处于待命状态。然而,两个相同种类的任务之间必须有长度为整数 n 的冷却时间,因此至少有连续 n 个单位时间内 CPU 在执行不同的任务,或者在待命状态。你需要计算完成所有任务所需要的 最短时间 。

比如:
输入:tasks = ["A","A","A","B","B","B"], n = 2
输出:8
解释:A -> B -> (待命) -> A -> B -> (待命) -> A -> B
在本示例中,两个相同类型任务之间必须间隔长度为 n = 2 的冷却时间,而执行一个任务只需要一个单位时间,所以中间出现了(待命)状态。

输入: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

1.2. 思路

这道题我一开始写错了。。。
这个是我的错误代码,对于AAABBBCCCDDE,n = 2,这种情况,我的代码中构造的任务调度方式为:ABC|ABC|ABC|DE |D,一共需要13个单位时间。
但实际上12个单位时间就够了。。。这明显是我任务调度方式安排的不对。。。

    //错误代码
    public static int leastInterval(char[] tasks, int n) {
        if (tasks == null) return 0;
        if (tasks.length < 2 || n == 0) return tasks.length;
        int bucketNum = 0;
        int fullNum = 0;
        int[] buckets = new int[tasks.length];
        int bucketSize = n + 1;
        int[] times = new int[26];
        for (int i = 0; i < tasks.length; i++) {
            times[tasks[i] - 'A']++;
        }

        for (int i = 0; i < 26; i++) {
            if (times[i] > 0) {
                int end = fullNum + times[i];
                for (int j = fullNum; j < Math.min(end, bucketNum); j++) {
                    if (++buckets[j] == bucketSize) {
                        fullNum++;
                    }
                }
                for (int j = bucketNum; j < end; j++) {
                    buckets[bucketNum++] = 1;
                }
            }
        }
        return (bucketNum - 1) * bucketSize + buckets[bucketNum - 1];
    }

这道题其实是个极限的思想,关键在于相同任务的执行时间间隔:

  1. 如果这个间隔足够大,那么这时,最小的执行时间就为(count[25] - 1)× (n+1)+ maxCount
  2. 如果这个间隔不够大,导致(count[25] - 1)×(n+1)+ maxCount小于taskTotalCount,那么可以通过某种方式证明,此时的执行时间为taskTotalCount

第二点证明如下:
在第二点的情况下,那么产生ceil(totalTaskCount / (n + 1))个时间间隔(因为此时(count[25] - 1) × (n + 1) + 1 < taskTotalCount,所以ceil(totalTaskCount / (n + 1))一定不小于count[25],然后将排序好的任务按照循环时间段的方式来填:

对于AAABBBCCCDDE,n = 2
---/---/---/---
A--/A--/A--/B--
AB-/AB-/AC-/BC-
ABC/ABD/ACD/BCE

按照这种方式来填的话,只有最后一个时间段才可能填不满,并且一定满足除了出现次数最多的任务的其他任务的时间间隔的需求:

  • ceil(totalTaskCount / (n + 1))count[25]相等:由于是次数多的任务先填,次数小的任务后填,所以不可能出现次数和count[25]相等的其他任务第一次填的时间段不是第一个时间段导致时间间隔不满足要求的情况。

  • ceil(totalTaskCount / (n + 1))大于count[25],那么很显然,每种任务的时间间隔要求都能满足。

    public int leastInterval(char[] tasks, int n) {
                int[] count = new int[26];
        for (int i = 0; i < tasks.length; i++) {
            count[tasks[i]-'A']++;
        }//统计词频
        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);

    }
posted @ 2022-05-23 15:43  迈吉  阅读(254)  评论(0)    收藏  举报