题目: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 完成所有任务所需的最少时间单位。以下是解法的详细解释:
- 首先,创建一个哈希映射
map来存储每个任务的出现次数。 - 遍历输入的
tasks数组,将每个任务出现的次数累加到map中。 - 创建一个优先队列
pq,用于存储任务的频次。这里使用了一个自定义比较器(a, b) -> b - a以实现降序排序,即优先队列的顶部始终是最高频次的任务。 - 将
map中的所有值(任务频次)添加到优先队列pq中。 - 初始化结果计数器
res为 0。 - 当优先队列
pq不为空时,执行以下操作: a. 创建一个临时列表temp,用于存储每轮执行的任务。 b. 遍历优先队列,取出最多n + 1个任务,将它们添加到临时列表temp中。这里n + 1是因为同一个任务之间至少需要n个时间单位的间隔。 c. 遍历临时列表temp,将每个任务的频次减一。如果任务的频次仍然大于 0,则将更新后的频次重新添加到优先队列pq中。 d. 如果优先队列pq为空,说明所有任务都已经完成,将temp.size()加到结果计数器res中。否则,将n + 1加到结果计数器res中,表示 CPU 在本轮中执行了任务或处于空闲状态。 - 返回结果计数器
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,所以我们的目的就是确定需要等待多少空闲时间。
以下是解法的详细解释:
- 首先,创建一个大小为 26 的整数数组
freqs来表示 26 个字母任务的频次。 - 遍历输入的
tasks数组,将每个任务出现的次数累加到freqs数组中。 - 对
freqs数组进行升序排序。这样,最后一个元素(下标为 25)将是最高频次的任务。 - 计算最高频次任务的空闲时间:
int max = freqs[25] - 1;。这里我们减一是因为最后一个最高频次任务之后不需要等待。 - 初始化空闲时间计数器:
int idleCount = max * n;。在最高频次任务之间的每个空档期都需要等待 n 个时间单位。 - 从后向前遍历
freqs数组的剩余元素(下标 24 到 0),减去每个任务所占用的空闲时间。为避免超过实际空闲时间,我们使用Math.min(max, freqs[i])。 - 如果
idleCount大于 0,说明还有剩余的空闲时间。否则,将idleCount设置为 0,表示没有额外的空闲时间。 - 返回
tasks.length + idleCount作为完成所有任务所需的最少时间。
首先处理频次最高的任务,然后处理次高频次的任务,以此类推。这样可以最大限度地减少空闲时间,并使得总时间最短。