好的,我们这次来详细讲解 LeetCode 第 621 题:任务调度器(Task Scheduler)。
虽然题目名称在已讲列表里出现过,但可能之前的讲解不够系统,我们这次把它拆解得非常细致,确保你能透彻掌握。
1. 题目描述
给你一个用字符数组 tasks 表示的 CPU 需要执行的任务列表,其中每个字母表示一种不同种类的任务。
任务可以以任意顺序执行,并且每个任务都可以在 1 个单位时间内执行完。
在任何一个单位时间,CPU 可以完成一个任务,或者处于待命状态。
然而,两个相同种类的任务之间必须有长度为整数 n 的冷却时间,
因此至少有连续 n 个单位时间内 CPU 在执行不同的任务,或者在待命状态。
你需要计算完成所有任务所需要的 最短时间。
示例 1:
输入:tasks = ["A","A","A","B","B","B"], n = 2
输出:8
解释:A -> B -> (待命) -> A -> B -> (待命) -> A -> B
(索引 0 1 2 3 4 5 6 7)
示例 2:
输入:tasks = ["A","A","A","B","B","B"], n = 0
输出:6
解释:这种情况下冷却时间为 0,直接按顺序做 A A A B B B 即可。
示例 3:
输入:tasks = ["A","A","A","A","A","B","B","B","B","B","C","C","C","C","C","D","D","D"], n = 1
输出:18
约束条件:
- 任务数量范围
[1, 10^4] tasks[i]是大写英文字母n的范围[0, 100]
2. 问题理解与抽象
我们先理解一下规则:
- 相同任务之间必须间隔至少
n个单位时间。 - 在这
n个间隔时间里,可以执行其他不同的任务,或者 CPU 待命。 - 目标是总时间最短。
直觉:出现次数最多的任务会制约整个调度,因为它的执行间隔必须满足冷却要求。
3. 思路推导(循序渐进)
3.1 特殊情况
如果 n = 0,没有冷却时间,总时间就是任务总数 len(tasks)。
3.2 一般情况分析
假设任务种类有 k 种,统计每个任务出现的次数,找到出现次数最多的任务,设其次数为 maxFreq。
第一步:安排出现次数最多的任务
我们把出现次数最多的任务称为 A,它需要执行 maxFreq 次。
执行第 1 个 A 后,必须等 n 个单位时间才能执行下一个 A。
因此,两个 A 之间至少有 n 个其他任务/待命。
那么,最后一个 A 执行完后,后面不需要再等冷却。
所以 A 本身占用的时间片是:
- 第一次执行:时间点 0
- 第二次执行:时间点 0 + (n+1)
- 第三次执行:时间点 0 + 2*(n+1)
- ...
- 第
maxFreq次执行:时间点 0 + (maxFreq-1)*(n+1)
所以,至少需要的时间是:(maxFreq - 1) * (n + 1) + 1
(最后一个任务执行完就结束,所以最后一个时间段只占 1 个单位)
第二步:安排其他任务
在 A 的冷却间隔中,可以插入其他任务。
如果还有任务次数和 A 一样多(比如 B 也出现 maxFreq 次),那么最后一个 B 必须紧接在最后一个 A 后面(或并行安排),这样会多占用一个时间单位。
因此,如果有 countMax 个任务出现次数都是 maxFreq,那么最终公式修正为:
min_time = (maxFreq - 1) * (n + 1) + countMax
其中 countMax 是出现次数等于 maxFreq 的任务种类数。
第三步:考虑任务很多,冷却间隔被填满的情况
有可能任务种类非常多,多到冷却间隔可以被完全填满,并且还有任务剩下,这时候总时间实际上就是任务总数,因为不需要额外待命时间。
所以最终答案是:
max( len(tasks), (maxFreq - 1) * (n + 1) + countMax )
4. 举例验证
示例 1
tasks = ["A","A","A","B","B","B"], n = 2
- 统计频率:A 出现 3 次,B 出现 3 次,maxFreq = 3, countMax = 2
- 计算:(3-1)(2+1) + 2 = 23 + 2 = 8
- 任务总数 6,max(6,8) = 8 ✅
示例 2
n = 0
- 公式:(3-1)(0+1) + 2 = 21 + 2 = 4
- 但任务总数 6,max(6,4) = 6 ✅
示例 3
tasks = [A×5, B×5, C×5, D×3], n=1
- maxFreq = 5, countMax = 3(A,B,C 都是 5 次)
- 公式:(5-1)(1+1) + 3 = 42 + 3 = 11
- 但任务总数 5+5+5+3=18,max(18,11)=18 ✅
5. 算法步骤
- 统计每个任务的频率。
- 找到最大频率
maxFreq。 - 统计有多少个任务频率等于
maxFreq,记为countMax。 - 计算
result = max( len(tasks), (maxFreq - 1) * (n + 1) + countMax )。 - 返回
result。
6. 代码实现(Python)
def leastInterval(tasks, n):
from collections import Counter
freq = list(Counter(tasks).values())
maxFreq = max(freq)
countMax = freq.count(maxFreq)
return max(len(tasks), (maxFreq - 1) * (n + 1) + countMax)
7. 复杂度分析
- 时间复杂度:O(N),N 是任务数量,统计频率和找最大值都是 O(N)。
- 空间复杂度:O(1),因为任务是大写字母,最多 26 种,freq 列表长度最多 26。
8. 为什么这个公式有效?
核心思想是桶思想:
- 想象把执行时间分成多个桶,每个桶容量为 n+1(一个任务 + n 个冷却位)。
- 桶的数量由最高频任务决定:有 maxFreq 个任务,就形成 maxFreq 个桶?不,其实是 maxFreq - 1 个完整桶(每个桶 n+1 时间),加上最后一个桶只装最高频任务(长度 countMax)。
- 如果任务数很多,桶装不下,说明不需要待命,总时间就是任务数。
这样讲解是否清晰?有没有哪一步需要我再展开说明?