在问 · 算法题解

621. 任务调度器

中等栈与堆大根堆 + 冷却队列模拟力扣原题 ↗

题目描述

输入字符数组 tasks,每个元素是一个大写字母,代表 CPU 要执行的一个任务,字母相同的任务属于同一种。另给一个非负整数 n 表示冷却时间。

CPU 每个时间单位要么执行一个任务,要么空闲待命。任务可以按任意顺序安排,但同一种任务的两次执行之间至少要隔开 n 个时间单位。

返回执行完全部任务所需的最少时间单位数,空闲的时间单位也计算在内。

示例 1
输入:tasks = ["A","A","A","B","B","B"], n = 2
输出:8

解释:一种安排是 A B 待命 A B 待命 A B,共 8 个单位。

示例 2
输入:tasks = ["A","C","A","B","D","B"], n = 1
输出:6

解释:例如 A B C D A B,不需要待命。

示例 3
输入:tasks = ["A","A","A","B","B","B"], n = 3
输出:10

解释:例如 A B 待命 待命 A B 待命 待命 A B,共 10 个单位。

数据范围

  • 1 <= tasks.length <= 10^4
  • tasks[i] 是大写英文字母。
  • 0 <= n <= 100

解题思路:大根堆 + 冷却队列模拟

贪心:每个时刻都执行“剩余次数最多且已经冷却好”的任务。剩余次数最多的任务最难安排,越早做越不容易在最后留下大段待命。用最大堆存可执行任务(按剩余次数),用一个队列存冷却中的任务以及它能再次执行的时刻。每过一个时间单位:堆非空就弹出堆顶执行,若还有剩余就进入冷却队列,解冻时刻 = 当前时刻 + n;堆为空说明只能待命,直接把时间跳到队首解冻的时刻。最后检查队首是否在这一刻解冻,解冻就放回堆中。

为什么正确

冷却队列按解冻时刻递增(因为入队时刻递增),堆里都是此刻可以执行的任务。

复杂度分析

时间 O(T log 26),T 为任务总数(待命时直接跳时间,不逐格模拟);空间 O(26)。

易错点

冷却 n 表示两次相同任务之间要隔 n 个单位,所以解冻时刻是 time + n,下一个可执行时刻是 time + n + 1:在本轮末尾解冻并放回堆,正好赶上下一轮。另外,公式解 (maxCnt−1)(n+1)+并列最大个数 也可以,但要和任务总数取最大值。

参考代码

Python、Java、C++、Go、JavaScript、TypeScript 六种语言,逻辑一致。

class Solution:
    def leastInterval(self, tasks: List[str], n: int) -> int:
        heap = [(-c, t) for t, c in collections.Counter(tasks).items()]
        heapq.heapify(heap)
        wait = collections.deque()
        time = 0
        while heap or wait:
            time += 1
            if heap:
                c, t = heapq.heappop(heap)
                if c + 1 < 0:
                    wait.append((c + 1, t, time + n))
            else:
                time = wait[0][2]
            if wait and wait[0][2] == time:
                c, t, _ = wait.popleft()
                heapq.heappush(heap, (c, t))
        return time

载入编辑器,自己跑一遍 →

其他解法

堆模拟每一步贪心地选剩余最多的任务,能得到调度过程;公式法直接由最高频任务算出下界,并说明这个下界可以达到。

解法 2:桶思想公式 时间 O(T),空间 O(26)

设出现次数最多的任务出现 maxCount 次,共有 maxTasks 种任务达到这个次数。把时间轴切成 maxCount 个桶,前 maxCount − 1 个桶长 n + 1,最后一个桶只放这 maxTasks 个任务,总长 (maxCount − 1)·(n + 1) + maxTasks。其他任务按列填入空位即可满足冷却。空位不够时可以加宽桶,完全不需要待命,答案就是任务总数 T。两者取较大值。

只需一遍计数,代码最短。但只输出总时间,不给出具体执行顺序,并且正确性需要单独证明。

class Solution:
    def leastInterval(self, tasks: List[str], n: int) -> int:
        cnt = [0] * 26
        for t in tasks:
            cnt[ord(t) - 65] += 1
        maxCount = max(cnt)
        maxTasks = cnt.count(maxCount)
        return max(len(tasks), (maxCount - 1) * (n + 1) + maxTasks)

栈与堆 专题的其他题目

查看 栈与堆 全部题目 →