621. 任务调度器
难度中等336
给定一个用字符数组表示的 CPU 需要执行的任务列表。其中包含使用大写的 A - Z 字母表示的26 种不同种类的任务。任务可以以任意顺序执行,并且每个任务都可以在 1 个单位时间内执行完。CPU 在任何一个单位时间内都可以执行一个任务,或者在待命状态。
然而,两个相同种类的任务之间必须有长度为 n 的冷却时间,因此至少有连续 n 个单位时间内 CPU 在执行不同的任务,或者在待命状态。
你需要计算完成所有任务所需要的最短时间。
示例 :
输入:tasks = ["A","A","A","B","B","B"], n = 2 输出:8 解释:A -> B -> (待命) -> A -> B -> (待命) -> A -> B. 在本示例中,两个相同类型任务之间必须间隔长度为 n = 2 的冷却时间,而执行一个任务只需要一个单位时间,所以中间出现了(待命)状态。
提示:
[1, 10000]
。n
的取值范围为 [0, 100]
。这道题说实话半天没转过弯来怎么用队列做
想了半天也是用数组统计任务种类,sort排序,还以为用队列能更快,想了半天也就感觉priorityqueue还有点边
不过还是无思路,好家伙,一看官方题解,队列只是充当排序的工具,怎么?sort它不香吗?Array它不快吗?
然后说实话,解法1,2我都没看懂,看了解法三,才迷迷糊糊。
直到看到这个方法(from ant-chen):
核心思想就是找到出现次数最多的字符(记为字符x),把那个按间隔排开。其他字符往间隔插入。分为几种情况。
1 有次数和x一样的字符,这些字符会加长最终长度。因为这些字符的最后一个会排在x的后面
2 空格不够的,会加大最终长度。因为要在间隔插入空格。
3 其余情况不会增加最终长度
如: ['A','A','A','A','B','B','B','B','C','C','D','D','E'] 2
先把A排开, 变成 A - - A - - A - - A
插入B,变成 A B - A B - A B - A B (由于B和A一样是三次,故最后长度+1)
插入C,变成 A B C A B C A B - A B (此时还有空格,长度不变)
插入第一个D, 变成 A B C A B C A B D A B (此时还有空格,长度不变)
插入第二个D, 此时无空格,D随意插入(只要不在上一个D旁边),长度必然+1,如:A B C D A B C A B D A B E
插入E, 此时无空格,E随意插入位置,长度必然+1,如:A B C D E A B C A B D A B E
具体计算见代码
class Solution {
public int leastInterval(char[] tasks, int n) {
//统计出现次数,排序
int[] counts = new int[26];
for(char ch: tasks){
counts[ch-'A']++;
}
Arrays.sort(counts);
int maxCycle = counts[25]; //找到出现次数最多的是多少次
int emptyPosition = (maxCycle-1) * (n + 1); //最少需要的初始位置数(没把最后一个 maxCycle计入)
int result = emptyPosition; //最后结果必不少于空位数
int element = 0; //所有任务需要占据的总位置数
for(int count : counts){
if(count == maxCycle){
result++; //最长的任务有几个,后面续几个位置
element += count-1; //该任务需要占据的初始位置数(减去了后面续的一个位置)
}else{
element += count; //不是最长的任务后面不给续,所以都计入
}
}
if(element <= emptyPosition){ //如果要占的数字小于初始数,就直接返回结果
}else{
return result + (element - emptyPosition);//否则返回结果+多出的个数
}
}
}
其实这个代码又可以用一个公式直接代替,使用公式的代码(python解法)如下:Hash其实是个字典
class Solution:
def leastInterval(self, tasks: List[str], n: int) -> int:
Hash = {}
for i in tasks:
Hash[i] = Hash.get(i, 0) + 1
task_max = max(Hash.values())
residual = 0
for key in Hash.keys():
if Hash[key] == task_max:
residual += 1
res = (task_max-1) * (n+1) + residual
return res if res >= len(tasks) else len(tasks)
如果想看懂官方解法1,2,可以看一下这个代码(go-go-49)
class Solution {
public int leastInterval(char[] tasks, int n) {
int []num=new int[26];
for(char c:tasks)
{
num[c-'A']++;//统计个数
}
int time=0;
Arrays.sort(num);
while (num[25]>0)
{
int i=0;
while (i<=n)//分配个数最多的n个任务
{
if(num[25]==0)//全部分配完
break;
if(i<26&&num[25-i]>0)//分配任务
num[25-i]--;
time++;
i++;
}
Arrays.sort(num);
}
return time;
}
}
为了再次学习队列知识,把用优先队列的解法2放一下
在选择每一轮中的任务时,我们也可以用优先队列(堆)来代替排序。在一开始,我们把所有的任务加入到优先队列中。在每一轮,我们从优先队列中选择最多 n + 1 个任务,把它们的数量减去 1,再放回堆中(如果数量不为 0),直到堆为空。
public class Solution {
public int leastInterval(char[] tasks, int n) {
int[] map = new int[26];
for (char c: tasks)
map[c - 'A']++;
PriorityQueue < Integer > queue = new PriorityQueue < > (26, Collections.reverseOrder());
for (int f: map) {
if (f > 0)
queue.add(f);
}
int time = 0;
while (!queue.isEmpty()) {
int i = 0;
List < Integer > temp = new ArrayList < > ();
while (i <= n) {
if (!queue.isEmpty()) {
if (queue.peek() > 1)
temp.add(queue.poll() - 1);
else
queue.poll();
}
time++;
if (queue.isEmpty() && temp.size() == 0)
break;
i++;
}
for (int l: temp)
queue.add(l);
}
return time;
}
}
在想用什么队列能实现思路时检索的一些队列相关资料
1,java队列——queue详细分析
2,Java的优先队列PriorityQueue详解
3,Java双向队列Deque栈与队列
4,Java ArrayDequeDescendingIterator()使用及代码示例
5,Java Vector遍历的五种方法