贪心-优先队列-模拟-任务调度器
2020-03-10 17:22:21
问题描述:
给定一个用字符数组表示的 CPU 需要执行的任务列表。其中包含使用大写的 A - Z 字母表示的26 种不同种类的任务。任务可以以任意顺序执行,并且每个任务都可以在 1 个单位时间内执行完。CPU 在任何一个单位时间内都可以执行一个任务,或者在待命状态。
然而,两个相同种类的任务之间必须有长度为 n 的冷却时间,因此至少有连续 n 个单位时间内 CPU 在执行不同的任务,或者在待命状态。
你需要计算完成所有任务所需要的最短时间。
示例 1:
输入: tasks = ["A","A","A","B","B","B"], n = 2
输出: 8
执行顺序: A -> B -> (待命) -> A -> B -> (待命) -> A -> B.
注:
任务的总个数为 [1, 10000]。
n 的取值范围为 [0, 100]。
问题求解:
解法一:贪心
从数据规模可以看出时间复杂度应该是O(n) / O(nlogn)。很容易想到的是要先将频率计算出来,并且按照频率进行排序,频率最高的字符至少会占用(freq_max - 1) * (n + 1) + 1个时间周期。
如果有k个频率均为最高,那么上述的公式就变成了(freq_max - 1) * (n + 1) + k,在这种情况下,低频率的字符完全放入了中间的空当中,甚至还会有盈余。
那么还有一种情况就是,中间的空档并不够放置所有剩余的字符,那么此时最终的解就是len(tasks),因为剩余的字符可以任意的插入不同slot的末尾,并且保证空闲是足够的。
算法时间复杂度:O(nlogn)
public int leastInterval(char[] tasks, int k) { int n = tasks.length; int[] freq = new int[26]; for (char c : tasks) freq[c - 'A'] += 1; Arrays.sort(freq); int idx = 25; while (idx >= 0 && freq[idx] == freq[25]) idx -= 1; int common = 25 - (idx + 1) + 1; return Math.max(n, (freq[25] - 1) * (k + 1) + common); }
解法二:优先队列
本题除了上述的贪心解法,还可以使用优先队列去模拟结果,使用优先队列的解更加直观。我们每次从队列中取k + 1个最多的任务去做,全部任务都执行完成即可。
时间复杂度:O(time) 和最终的任务序列长度有关。
public int leastInterval(char[] tasks, int k) { int n = tasks.length; int[] freq = new int[26]; for (char c : tasks) freq[c - 'A'] += 1; PriorityQueue<Integer> pq = new PriorityQueue<>(Collections.reverseOrder()); for (int i = 0; i < 26; i++) if (freq[i] != 0) pq.add(freq[i]); int res = 0; while (!pq.isEmpty()) { List<Integer> temp = new ArrayList<>(); for (int i = 0; i <= k; i++) { if (!pq.isEmpty()) { if (pq.peek() > 1) { temp.add(pq.peek() - 1); } pq.poll(); } res += 1; if (pq.isEmpty() && temp.size() == 0) break; } for (Integer i : temp) pq.add(i); } return res; }