Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情。
题目:给定一个字符数组tasks表示需要执行的任务列表,每个字母代表不同任务。所有任务可以顺序执行并且运行时间都相同,但两个相同的任务存在长度为n的冷却期,要求计算完成所有任务所需消耗的最短时间。
例如:
输入:tasks = ["A","A","A","A","A","A","B","C","D","E","F","G"], n = 2
输出:16
解释:一种可能的解决方案是:
A -> B -> C -> A -> D -> E -> A -> F -> G -> A -> (待命) -> (待命) -> A -> (待命) -> (待命) -> A
解题思路
根据题目意思手动模拟得到答案很简单,例如对于输入["A","A","A","B","B","B"],n=2。
手动模拟为先统计每个任务的数量,得到A:3, B:3,之后根据数量进行排序,数量大的排前面,首先输出任务A,更新A的数量,再往后选b,而n=2,但此时集合已经没有任务了,因此必须等待,时间可直接加n+1。之后一直循环,直到所有任务的数量为0。
思路并不难,但在代码中实现却存在困难,统计数量可以使用map来统计,之后需要将map进行排序,可行的方式是将map存入list,之后调用list.sort()自己定义排序规则进行排序,这样有序的任务序列就得到了,但每次删除任务之后还需要将任务再次进行排序,这样肯定不行。
此时就希望找到一种数据结构,这个数据结构能在每次新增任务都自动进行排序,优先级队列就进入考虑,我们可以每次删除堆顶的任务,更新任务数量,判断数量是否等于0,不等于则代表此任务没结束需要再次执行,此时使用list将任务存起来,而相同任务间隔则很好解决,根据n进行弹出堆元素,如果堆空了,则直接增加时间为n+1即可,可得代码如下:
public int leastInterval(char[] tasks, int n) {
HashMap<Integer, Integer> map = new HashMap<>();
for(Character c:tasks){
map.put(c-65, map.getOrDefault(c-65, 0) + 1);
}
PriorityQueue<int[]> queue = new PriorityQueue<>((t1, t2) -> t2[1] - t1[1]);
for(Map.Entry<Integer, Integer> entry:map.entrySet()){
int key = entry.getKey();
int value = entry.getValue();
queue.offer(new int[]{key, value});
}
int res = 0;
int k = 0;
ArrayList<int[]> tempList = new ArrayList<>();
while (!queue.isEmpty()){
tempList.clear();
k = n+1;
while(!queue.isEmpty()&&k>0){
int[] poll = queue.poll();
poll[1]--;
if(poll[1]!=0) tempList.add(poll);
k--;
}
queue.addAll(tempList);
res += n+1;
}
return res-k;
}
代码的最后减k在这里解释一下:
因为最后一次可能出现栈空的情况,例如最终只剩下了一个任务A,此时时间只有1,但最后却加了n+1的时间,而实际时间应该是n+1-k,因此最后要减k。
最终耗时28ms,超越16%的Java用户。
题解还有一种黑科技有点难想,下面只贴上代码:
public int leastInterval2(char[] tasks, int n) {
int[] count = new int[26];
for(Character c:tasks){
count[c-65] ++;
}
Arrays.sort(count);//词频排序,升序排序,count[25]是频率最高的
int maxCount = 0;
//统计有多少个频率最高的字母
for (int i = 25; i >= 0; i--) {
if(count[i] != count[25]){
break;
}
maxCount++;
}
//公式算出的值可能会比数组的长度小,取两者中最大的那个
return Math.max((count[25] - 1) * (n + 1) + maxCount , tasks.length);
}