题目描述:
给你一个用字符数组 tasks
表示的 CPU 需要执行的任务列表。其中每个字母表示一种不同种类的任务。任务可以以任意顺序执行,并且每个任务都可以在 1 个单位时间内执行完。在任何一个单位时间,CPU 可以完成一个任务,或者处于待命状态。
然而,两个 相同种类 的任务之间必须有长度为整数 n
的冷却时间,因此至少有连续 n
个单位时间内 CPU 在执行不同的任务,或者在待命状态。
你需要计算完成所有任务所需要的 最短时间 。
示例 1:
1 | 输入:tasks = ["A","A","A","B","B","B"], n = 2 |
示例 2:
1 | 输入:tasks = ["A","A","A","B","B","B"], n = 0 |
示例 3:
1 | 输入:tasks = ["A","A","A","A","A","A","B","C","D","E","F","G"], n = 2 |
提示:
- $1 <= task.length <= 10^4$
tasks[i]
是大写英文字母n
的取值范围为[0, 100]
链接:
https://leetcode-cn.com/problems/task-scheduler
题目分析
我们最先想到的应该是贪心的做法,也就是任务量最多的任务先完成。而执行一个任务之后都要等待 n
时间过后才能继续该任务,那么我们可以将 n+1
时间视为一个执行周期。假设任务量最多的任务次数是 maxtimes
,那么我们至少就需要 maxtimes
个执行周期。假设任务数最多的任务是 A,需要执行 6 次,等待时间 n=2
。那么我们的执行示意图就如下图所示。当然最后一个执行周期执行完 A 就可以结束了。总时间是 (2+1)*(6-1)+1=16
然后我们就可以对每个执行周期中的其他空位继续填入其他任务,我们先假设任务的总量可以安排到这些执行周期内。可以看到任务 B 执行次数跟 A 一样都是最大值,需要排到最后一个执行周期。而任务 C 是不会对结果有任何影响的。总时间是 (2+1)*(6-1)+2=17
。这个时候即使将 C 下面的三个“等待”时间填满也可以。
假设任务量最多的任务共有 diff
种,那么假设总的任务量没有超过 (n+1)*(maxtimes-1)+diff
时,我们肯定是能够按照这种方法将其安排进这些任务列表里的。
如果任务量超过了这个大小呢?比如下面所示,A、B、C、D 任务已经填满了这个表,而我们还有任务 F。这个时候其实我们直接将任务 F 加入到前面的执行周期就好了,为什么呢?执行周期的限制其实是对每列任务的限制,比如在这里,第一个周期的 A 执行过后,至少需要等待 2 个时间才能继续执行 A,那我们等到 3 个时间当然也是没有任何问题的。因此我们可以直接对每个执行周期任意延长。
注意这里“延长”执行周期并不是对所有的执行周期都延长,而是只延长需要的,比如这里,前两个执行周期延长到了 4,而下面的执行周期依然是 3。这个时候我们可以发现,其实我们需要的时间数就是所有的任务数,这个时候我们已经不需要任何的“待命”时间了。
通过上面的分析,我们知道所需时间要么是 (n+1)*(maxtimes-1)+diff
,要么是任务数,是两者中更大的那个。那么我们就只需要计算出任务量最多的任务,以及它们的种数。我们使用一个哈希表来记录每个任务的任务量,之后再遍历哈希表找出任务量最多的任务种类即可。
1 | class Solution { |
时间复杂度:$O(n)$,其中 $n$ 是任务数。我们只需要遍历一次任务进行记录,再遍历一次哈希表获取结果,哈希表的大小不会超过 $n$。
空间复杂度:$O(n)$,其中 $n$ 是任务数。也就是哈希表的开销。
PS:上面的三个图来自 Leetcode 会员 popopop 发表的题解。由于我懒得画图,因此直接用同样的例子进行解释,然后就套用了 ta 的图,在这里表示感谢。