题目描述
思路解析动画文字版
很多人第一反应是套优先队列:每一轮把还没冷却的任务丢进大顶堆,弹出当前剩余次数最多的那个先做,再把它扔进冷却队列等 n 轮。这是教科书做法,能过,但要同时维护堆和冷却队列,代码长、边界多。
转折:堆每轮总是优先消化最高频任务,所以最终排布一定是「最高频任务搭骨架,其余任务来填空」。既然结果形状固定,就不必真的跑堆,用计数一步算出长度。这就是本题从「堆模拟」简化成「贪心计数」的关键。
统计频次:A=3,B=3:先只数任务种类和出现次数,原始顺序不重要。固定 8 格是为了后面摆最短时间轴。
最高频次数 max_freq = 3:出现次数最多的任务最难安排,它决定冷却骨架要分几层。这里 A、B 都是 3 次。
先放最高频任务 A:隔出两个冷却位:3 个 A 之间各要隔 2 格,先摆出 A 空 空 A 空 空 A。中间的空位就是要填的冷却槽。
B 也达到最高频:和 A 并列进每一层:B 也出现 3 次,和 A 并列。所以尾部那一层不只放 A,还得放 B,这就是公式末尾要加 max_count 的原因。
空位填不满:剩下两个 idle:样例一只有 A、B 两种任务,剩下的冷却空位没东西填,只能待命,于是冒出两个 idle。
公式数出骨架 frame = 8:前面 (max_freq-1) 个完整块,每块长度 n+1,也就是一个任务加 n 个槽;最后再补上 max_count 个尾巴任务。2×3+2 = 8。
和任务总数取最大值,样例一答案 8:所有任务都得执行完,答案至少是 len(tasks)=6;而冷却骨架要求 8,更大,所以样例一返回 8。
样例二对照:种类多,其他任务填满空位:换样例二:A、B 各 2 次,骨架只要 4,但还有 C、D 把空位填满了,不需要待命。此时 len=6 反而更大,答案就是 6。两个样例一对照,公式两端谁大取谁就清楚了。
调度题的通用直觉:最高频元素常常决定答案的下界,其他元素只是用来塞进冷却空位。抓住这条,就不必背公式。
边界三连:n=0 时一轮长度变 1、骨架就是总数;只剩一种任务时全靠 idle 撑长;多种并列时 max_count 要全数进去。
面试追问:把「堆的等价、两个变量的含义、idle 何时消失」讲成自己的话,就是这题的追问拿分点。
参考代码
from collections import Counterclass Solution: def leastInterval(self, tasks, n): counts = Counter(tasks).values() max_freq = max(counts) max_count = sum(1 for c in counts if c == max_freq) frame = (max_freq - 1) * (n + 1) + max_count return max(len(tasks), frame)复杂度
- 时间复杂度:O(m),m 是任务总数,Counter 扫一遍统计频次;之后只对 26 个字母求 max 和计数,是常数。
- 空间复杂度:O(1),只用一个长度 26 的计数数组,和任务数量无关。
可套用的代码模板
把本题抽象成可迁移骨架:数频次 → 取最高频 peak 和并列数 ties → 骨架 (peak-1)*(间隔+1)+ties,最后和总数取大。换成 767 重构字符串、贴海报隔行等题,套这三步只改「间隔」和「填空判定」即可。
# 「最高频搭骨架」三步模板:# 1) 数频次 2) 取最高频与并列数 3) 骨架 vs 总数cnt = 频次表(元素) # ① Counter / 数组peak = max(cnt) # ② 最高频次数ties = (cnt 中等于 peak 的个数) # ② 有几种并列最高frame = (peak - 1) * (间隔 + 1) + ties # ③ 骨架长度return max(总数, frame) # ③ 别忘和总数取大易错点
面试追问把动画讲成自己的话
追问如果面试官要你用优先队列做,怎么写?
追问公式里的 max_freq 和 max_count 各是什么?
追问什么时候根本不会有 idle?
这道题到这就讲完了。动画和文字是同一套思路——别光看,关掉页面自己默写一遍。然后顺着主线继续:
设计推特
LeetCode 355 · 中等 · 沿着 堆 / 优先队列 继续往下推进
把这道题真正学会,再走
图解算法年卡 ¥99 /年
- ✓本题每步动画的吴师兄语音讲解(全站陆续覆盖)
- ✓小欧带 8 步通关训练——追问到不看答案也能写对、能 30 秒讲给面试官听
- ✓学习报告,记录每道题的掌握程度
76k+ GitHub Star · 吴师兄开源图解算法,几十万开发者在看的算法讲解
想成体系刷透这类套路?去图解算法专题