ChatGPT带你刷题-Leetcode 621. Task Scheduler

177 阅读4分钟

题目:621.任务调度器

难度:中等

给定一个字符数组 tasks,表示 CPU 需要执行的任务,其中每个字母代表不同的任务。任务可以按任意顺序完成。每个任务需要一个时间单位来完成。在每个时间单位中,CPU 可以完成一个任务或者处于空闲状态。

然而,有一个非负整数 n,表示两个相同任务之间的冷却时间(数组中的相同字母),即任意两个相同任务之间至少有 n 个时间单位。

返回 CPU 完成所有给定任务所需的最少时间单位。

Example 1:

Input: tasks = ["A","A","A","B","B","B"], n = 2
Output: 8
Explanation: 
A -> B -> idle -> A -> B -> idle -> A -> B
There is at least 2 units of time between any two same tasks.

Example 2:

Input: tasks = ["A","A","A","B","B","B"], n = 0
Output: 6
Explanation: On this case any permutation of size 6 would work since n = 0.
["A","A","A","B","B","B"]
["A","B","A","B","A","B"]
["B","B","B","A","A","A"]
...
And so on.

Example 3:

Input: tasks = ["A","A","A","A","A","A","B","C","D","E","F","G"], n = 2
Output: 16
Explanation: 
One possible solution is
A -> B -> C -> A -> D -> E -> A -> F -> G -> A -> idle -> idle -> A -> idle -> idle -> A

方法一:

class Solution {
    public int leastInterval(char[] tasks, int n) {
        Map<Character, Integer> map = new HashMap<>();
        for(char task : tasks){
            map.put(task, map.getOrDefault(task, 0) + 1);
        }
        PriorityQueue<Integer> pq = new PriorityQueue<>((a, b) -> b - a);
        pq.addAll(map.values());
        int res = 0;
        while(!pq.isEmpty()){
            List<Integer> temp = new ArrayList<>();
            for(int i = 0; i < n + 1; i++){
                if(!pq.isEmpty()){
                    temp.add(pq.poll());
                }
            }
            for(int num : temp){
                if(--num > 0){
                    pq.offer(num);
                }
            }
            res += pq.isEmpty() ? temp.size() : n + 1;
        }
        return res;
    }
}

这个解法也是为了计算 CPU 完成所有任务所需的最少时间单位。以下是解法的详细解释:

  1. 首先,创建一个哈希映射 map 来存储每个任务的出现次数。
  2. 遍历输入的 tasks 数组,将每个任务出现的次数累加到 map 中。
  3. 创建一个优先队列 pq,用于存储任务的频次。这里使用了一个自定义比较器 (a, b) -> b - a 以实现降序排序,即优先队列的顶部始终是最高频次的任务。
  4. map 中的所有值(任务频次)添加到优先队列 pq 中。
  5. 初始化结果计数器 res 为 0。
  6. 当优先队列 pq 不为空时,执行以下操作: a. 创建一个临时列表 temp,用于存储每轮执行的任务。 b. 遍历优先队列,取出最多 n + 1 个任务,将它们添加到临时列表 temp 中。这里 n + 1 是因为同一个任务之间至少需要 n 个时间单位的间隔。 c. 遍历临时列表 temp,将每个任务的频次减一。如果任务的频次仍然大于 0,则将更新后的频次重新添加到优先队列 pq 中。 d. 如果优先队列 pq 为空,说明所有任务都已经完成,将 temp.size() 加到结果计数器 res 中。否则,将 n + 1 加到结果计数器 res 中,表示 CPU 在本轮中执行了任务或处于空闲状态。
  7. 返回结果计数器 res 作为完成所有任务所需的最少时间单位。

这个解法的主要思想是优先处理频次最高的任务,以确保在处理相同任务之间的冷却时间时,可以尽量插入其他任务。 并且利用有限队列,动态的获取当前出现频率最高的任务。这样可以最大限度地减少空闲时间,并使得总时间最短。

方法二:贪心算法

class Solution {
    public int leastInterval(char[] tasks, int n) {
        int[] times = new int[26];
        for(char c : tasks){
            times[c - 'A']++;
        }
        Arrays.sort(times);
        int max = times[25] - 1;
        int idleCount = max * n;
        for(int i = 24; i >= 0; i--){
            idleCount -= Math.min(max, times[i]);
        }
        idleCount = idleCount < 0? 0 : idleCount;
        return tasks.length + idleCount;
    }
}

这个解法基于贪心算法来计算 CPU 完成所有任务所需的最少时间。这个解法的核心思想是所有任务总时间为做任务时间 + 空闲时间任务时间已知为tasks.length,所以我们的目的就是确定需要等待多少空闲时间。 以下是解法的详细解释:

  1. 首先,创建一个大小为 26 的整数数组 freqs 来表示 26 个字母任务的频次。
  2. 遍历输入的 tasks 数组,将每个任务出现的次数累加到 freqs 数组中。
  3. freqs 数组进行升序排序。这样,最后一个元素(下标为 25)将是最高频次的任务。
  4. 计算最高频次任务的空闲时间:int max = freqs[25] - 1;。这里我们减一是因为最后一个最高频次任务之后不需要等待。
  5. 初始化空闲时间计数器:int idleCount = max * n;。在最高频次任务之间的每个空档期都需要等待 n 个时间单位。
  6. 从后向前遍历 freqs 数组的剩余元素(下标 24 到 0),减去每个任务所占用的空闲时间。为避免超过实际空闲时间,我们使用 Math.min(max, freqs[i])
  7. 如果 idleCount 大于 0,说明还有剩余的空闲时间。否则,将 idleCount 设置为 0,表示没有额外的空闲时间。
  8. 返回 tasks.length + idleCount 作为完成所有任务所需的最少时间。

首先处理频次最高的任务,然后处理次高频次的任务,以此类推。这样可以最大限度地减少空闲时间,并使得总时间最短。