题目描述
思路解析
一句话答案:LeetCode 621 任务调度器有 O(N) 的公式解:设最高频任务出现 maxCount 次、并列最高频的有 maxTasks 个,最短总时长 = max((maxCount - 1) × (n + 1) + maxTasks, 任务总数)。前者是最高频任务撑起的骨架,后者兜底不需要空转的情况;空间 O(1)。
这道题的约束到底卡在哪
任务不限执行顺序、每个占一个时间单位,唯一的约束是同种任务两次执行之间至少隔 n 个单位;轮到没任务可干时只能插 idle 待命,而 idle 同样计入总时长。所以题目本质是排布问题:怎么安排才能让被迫空转的格子最少。直觉上,出现次数最多的任务是麻烦制造者——它和自己的冷却冲突次数最多,别的任务反而好办。
为什么最高频任务决定了骨架
盯住出现次数最多的任务,设它出现 maxCount 次。相邻两次之间至少隔 n 格,连它自己占的那一格算上,一个最小重复周期就是 n + 1 格——这也回答了「周期为什么是 n + 1 而不是 n」。前 maxCount - 1 次执行各自撑起一个完整周期,共 (maxCount - 1) × (n + 1) 格;最后一次执行完不必再等冷却,此时有几个任务并列最高频,末尾就各占一格,再加 maxTasks。这两截拼起来就是骨架长度,其余低频任务只负责往周期的空隙里填。
为什么还要和任务总数取较大值
骨架公式隐含一个假设:周期里的空隙填不满,需要 idle 补位。但任务种类足够多时,空隙全被占满之后还有剩余任务——把它们插进各个周期也不违反间隔约束,因为周期被撑得更宽,同种任务的间距只会更充裕。这种情况下一格 idle 都不需要,总时长就等于任务总数本身,骨架公式反而算少了。所以最终答案是 max(骨架, len(tasks)),漏掉这一步兜底是本题最常见的错。
贪心每轮挑剩余最多的为什么对
若用模拟来验证:每一轮的 n + 1 格里,从剩余次数最多的任务开始挑着放,放不满就 idle。优先消耗高频任务,是为了防止它们攒到最后挤成一团、只能靠大量空转硬隔开——先把最难安置的摊开,容易的自然见缝插针。用最大堆模拟这个贪心过程,答案与公式一致,公式正是对该过程的数学闭式。以示例 A、B 各 6 次,C 4 次,D、E 各 2 次,n 取 2 为例:骨架 (6 - 1) × 3 + 2 得 17,任务总数 20,取大为 20。
复杂度与容易翻车的细节
统计频次只需扫一遍任务表,时间 O(N);任务种类至多 26 个字母,计数数组是常数空间 O(1)。三个细节最容易错:并列最高频的任务必须全部计入 maxTasks,只算一个会把末轮算短;周期长度是 n + 1 不是 n;忘了与任务总数取 max。另外公式解只给最短时长、不输出具体排班,若面试官追问执行序列,再补最大堆逐轮模拟即可。
▶ 动画逐步走查(文字版)——想跟着上方动画一帧帧对照就展开
本例最高频是 A、B 各 6 次(并列 2 个)。骨架 = (6-1)×3+2 = 17;任务总数 = 20;取大 = 20。下面用「最大堆」一格格把它排出来。
第 1 周期 · 第 1 格:本轮挑剩余次数最多的任务,轮到放 A(它是最高频,撑骨架):现在 A 还剩 6 次,放下后它要等至少 2 格才能再放。
落子:第 1 周期第 1 格放 任务 A,时间走到 1。绿色撑起骨架。
第 1 周期 · 第 2 格:本轮挑剩余次数最多的任务,轮到放 B(它是最高频,撑骨架):现在 B 还剩 6 次,放下后它要等至少 2 格才能再放。
落子:第 1 周期第 2 格放 任务 B,时间走到 2。绿色撑起骨架。
第 1 周期 · 第 3 格:本轮挑剩余次数最多的任务,轮到放 C(它来填空隙):现在 C 还剩 4 次,放下后它要等至少 2 格才能再放。
落子:第 1 周期第 3 格放 任务 C,时间走到 3。橘色填补空隙,不浪费时间。
第 2 周期 · 第 1 格:本轮挑剩余次数最多的任务,轮到放 A(它是最高频,撑骨架):现在 A 还剩 5 次,放下后它要等至少 2 格才能再放。
落子:第 2 周期第 1 格放 任务 A,时间走到 4。绿色撑起骨架。
第 2 周期 · 第 2 格:本轮挑剩余次数最多的任务,轮到放 B(它是最高频,撑骨架):现在 B 还剩 5 次,放下后它要等至少 2 格才能再放。
落子:第 2 周期第 2 格放 任务 B,时间走到 5。绿色撑起骨架。
第 2 周期 · 第 3 格:本轮挑剩余次数最多的任务,轮到放 C(它来填空隙):现在 C 还剩 3 次,放下后它要等至少 2 格才能再放。
落子:第 2 周期第 3 格放 任务 C,时间走到 6。橘色填补空隙,不浪费时间。
第 3 周期 · 第 1 格:本轮挑剩余次数最多的任务,轮到放 A(它是最高频,撑骨架):现在 A 还剩 4 次,放下后它要等至少 2 格才能再放。
落子:第 3 周期第 1 格放 任务 A,时间走到 7。绿色撑起骨架。
第 3 周期 · 第 2 格:本轮挑剩余次数最多的任务,轮到放 B(它是最高频,撑骨架):现在 B 还剩 4 次,放下后它要等至少 2 格才能再放。
落子:第 3 周期第 2 格放 任务 B,时间走到 8。绿色撑起骨架。
第 3 周期 · 第 3 格:本轮挑剩余次数最多的任务,轮到放 C(它来填空隙):现在 C 还剩 2 次,放下后它要等至少 2 格才能再放。
落子:第 3 周期第 3 格放 任务 C,时间走到 9。橘色填补空隙,不浪费时间。
第 4 周期 · 第 1 格:本轮挑剩余次数最多的任务,轮到放 A(它是最高频,撑骨架):现在 A 还剩 3 次,放下后它要等至少 2 格才能再放。
落子:第 4 周期第 1 格放 任务 A,时间走到 10。绿色撑起骨架。
第 4 周期 · 第 2 格:本轮挑剩余次数最多的任务,轮到放 B(它是最高频,撑骨架):现在 B 还剩 3 次,放下后它要等至少 2 格才能再放。
落子:第 4 周期第 2 格放 任务 B,时间走到 11。绿色撑起骨架。
第 4 周期 · 第 3 格:本轮挑剩余次数最多的任务,轮到放 D(它来填空隙):现在 D 还剩 2 次,放下后它要等至少 2 格才能再放。
落子:第 4 周期第 3 格放 任务 D,时间走到 12。橘色填补空隙,不浪费时间。
第 5 周期 · 第 1 格:本轮挑剩余次数最多的任务,轮到放 A(它是最高频,撑骨架):现在 A 还剩 2 次,放下后它要等至少 2 格才能再放。
落子:第 5 周期第 1 格放 任务 A,时间走到 13。绿色撑起骨架。
第 5 周期 · 第 2 格:本轮挑剩余次数最多的任务,轮到放 B(它是最高频,撑骨架):现在 B 还剩 2 次,放下后它要等至少 2 格才能再放。
落子:第 5 周期第 2 格放 任务 B,时间走到 14。绿色撑起骨架。
第 5 周期 · 第 3 格:本轮挑剩余次数最多的任务,轮到放 E(它来填空隙):现在 E 还剩 2 次,放下后它要等至少 2 格才能再放。
落子:第 5 周期第 3 格放 任务 E,时间走到 15。橘色填补空隙,不浪费时间。
第 6 周期 · 第 1 格:本轮挑剩余次数最多的任务,轮到放 A(它是最高频,撑骨架):现在 A 还剩 1 次,放下后它要等至少 2 格才能再放。
落子:第 6 周期第 1 格放 任务 A,时间走到 16。绿色撑起骨架。
第 6 周期 · 第 2 格:本轮挑剩余次数最多的任务,轮到放 B(它是最高频,撑骨架):现在 B 还剩 1 次,放下后它要等至少 2 格才能再放。
落子:第 6 周期第 2 格放 任务 B,时间走到 17。绿色撑起骨架。
第 6 周期 · 第 3 格:本轮挑剩余次数最多的任务,轮到放 C(它来填空隙):现在 C 还剩 1 次,放下后它要等至少 2 格才能再放。
落子:第 6 周期第 3 格放 任务 C,时间走到 18。橘色填补空隙,不浪费时间。
第 7 周期 · 第 1 格:本轮挑剩余次数最多的任务,轮到放 D(它来填空隙):现在 D 还剩 1 次,放下后它要等至少 2 格才能再放。
落子:第 7 周期第 1 格放 任务 D,时间走到 19。橘色填补空隙,不浪费时间。
第 7 周期 · 第 2 格:本轮挑剩余次数最多的任务,轮到放 E(它来填空隙):现在 E 还剩 1 次,放下后它要等至少 2 格才能再放。
落子:第 7 周期第 2 格放 任务 E,时间走到 20。橘色填补空隙,不浪费时间。
全部排完!一共用了 20 个时间单位 = 答案 20。绿色(最高频)撑骨架、橘色填空隙、灰色是不得不待命的 idle。
n=0 或全不同时退化成总数;单一任务时全是 idle,骨架公式正好给出答案。
面试里能讲清「n+1 周期」和「何时退化成总数」这两点,就说明真懂了贪心结构,而不是背公式。
参考代码
from collections import Counterdef leastInterval(tasks, n): cnt = Counter(tasks) maxCount = max(cnt.values()) # 有多少个任务并列最高频 maxTasks = sum(1 for v in cnt.values() if v == maxCount) # 骨架长度 与 任务总数 取较大者 return max((maxCount - 1) * (n + 1) + maxTasks, len(tasks))复杂度
- 时间:O(N),N=任务总数,统计频次一遍;26 个字母的扫描是常数
- 空间:O(1),只用大小 26 的计数数组,与输入规模无关
易错点
面试追问把动画讲成自己的话
追问为什么是 (n+1) 一个周期,而不是 n?
追问什么时候答案等于任务总数、根本不用 idle?
追问最大堆模拟和公式法是什么关系?
这道题到这就讲完了。动画和文字是同一套思路——别光看,关掉页面自己默写一遍。然后顺着主线继续:
设计推特
LeetCode 355 · 中等 · 沿着 堆 / 优先队列 继续往下推进
把这道题真正学会,再走
图解算法年卡 ¥99 /年
- ✓本题每步动画的吴师兄语音讲解(全站陆续覆盖)
- ✓小欧带 8 步通关训练——追问到不看答案也能写对、能 30 秒讲给面试官听
- ✓学习报告,记录每道题的掌握程度
76k+ GitHub Star · 吴师兄开源图解算法,几十万开发者在看的算法讲解
想成体系刷透这类套路?去图解算法专题