题目链接: https://leetcode.com/problems/task-scheduler/description/
Description
Given a char array representing tasks CPU need to do. It contains capital letters A to Z where different letters represent different tasks.Tasks could be done without original order. Each task could be done in one interval. For each interval, CPU could finish one task or just be idle.
However, there is a non-negative cooling interval n that means between two same tasks, there must be at least n intervals that CPU are doing different tasks or just be idle.
You need to return the least number of intervals the CPU will take to finish all the given tasks.
Example 1:
Input: tasks = ["A","A","A","B","B","B"], n = 2
Output: 8
Explanation: A -> B -> idle -> A -> B -> idle -> A -> B.
Note:
- The number of tasks is in the range [1, 10000].
- The integer n is in the range [0, 100].
解题思路
记 cooling interval 加 1 为一个调度周期,最优的顺序应该为在每个调度周期内,依据task
数量,从多到少无重复依次执行,若无法填满整个调度周期,则剩下的空槽保留,开始下一轮调度,直到完成所有tasks
。
由于task
的名字只可能为26
个字母,并且结果与名字无关,因此用一个大小为26
的数组存储每种task
的数量,先统计出每种task
的数量,用数量代替每种task
来继续解题。
方法一:优先队列
大体思路为用优先队列模拟整个调度。声明一个权值大优先级高的优先队列,保证出队的顺序为从大到小。将每种task
统计出的数量入队。并且,在每一轮中,用一个数组temp
来记录已经出队且剩余数量大于 1 的task
,在该轮结束后重新入队,以此来确保当轮调度无重复。
模拟每一轮调度,若队列不为空则出队,times
加一,判断出队的task
剩余数量若不为 0,则存入数组中,等待下一轮开始前重新入队;若队列为空,先将上一轮temp
中的值重新入队,并且统计上一轮剩下的空槽数量加到times
中,再开始下一轮调度。直到所有tasks
都完成,即队列为空,返回times
即为结果。
方法二:统计空闲槽
例子,tasks = ["A","A","A","B","B","B","C"], n = 2
记task
数最大值为max_val
,空槽数最多为(max_val - 1) * n
,记为idles
A | - | - |
---|---|---|
A | - | - |
A |
实际空槽数为最大空槽数减去其他task
在除最后一轮以外的轮次中占用的槽数
A | B | C |
---|---|---|
A | B | - |
A | B |
因此,这种方法可以先计算出最大空槽数idles = (max_val - 1) * n
,然后对剩余的每一种task
,用idles - min(task_size, max_val - 1)
。另外,n 可能为 0,这样统计出的空槽数可能为负数,所以需要先判断空槽数是否大于 0,若大于则结果为空槽数加上tasks
总数,否则为tasks
总数。
方法三:优化版
与方法二类似,换了一种思路。不用统计空槽数,因为除了最后一轮以外的轮次所需时间肯定为n + 1
,这些轮次总共需要时间为(max_val - 1) * (n + 1)
,重点在最后一个轮次。最后一轮中,只有数量等于max_val
的task
才会在该轮中调度,因此只需要统计出数量等于max_val
的task
数量,用前面轮次的总时间加上这个数量即为最终结果。
另外,还要考虑特殊情况,若 n 为 0,则上述方法统计出的结果会小于tasks
总数。
举个例子,tasks = ["A","A","A","B","B","B"], n = 0
上述方法计算出的结果为 (3 - 1) * (0 + 1) + 2 = 4
,而正确结果应该是tasks
总数 6,因此最终结果应该为该方法算出的结果与tasks
总数之间的较大值。
Code
方法一:优先队列
class Solution {
public:
int leastInterval(vector<char>& tasks, int n) {
int times = 0, i = n + 1;
priority_queue<int, vector<int>, less<int>> pq;
vector<int> alpha(26);
for (char &ch: tasks) {
alpha[ch - 'A']++;
}
for (int count: alpha) {
if (count > 0) {
pq.push(count);
}
}
while (!pq.empty()) {
times += n - i + 1;
i = 0;
list<int> temp;
while (i <= n) {
if (!pq.empty()) {
if (pq.top() > 1) {
temp.push_back(pq.top() - 1);
}
pq.pop();
times++;
} else {
break;
}
i++;
}
for (auto count: temp) {
pq.push(count);
}
}
return times;
}
};
方法二:统计空闲槽
class Solution {
public:
int leastInterval(vector<char>& tasks, int n) {
vector<int> alpha(26);
for (char &ch: tasks) {
alpha[ch - 'A']++;
}
sort(alpha.begin(), alpha.end());
int max_val = alpha.back() - 1;
int idles = max_val * n;
for (int i = 24; i >= 0 && alpha[i] > 0; --i) {
idles -= min(max_val, alpha[i]);
}
return idles > 0 ? idles + tasks.size() : tasks.size();
}
};
方法三:优化版
class Solution {
public:
int leastInterval(vector<char>& tasks, int n) {
vector<int> alpha(26);
for (char &ch: tasks) {
alpha[ch - 'A']++;
}
sort(alpha.begin(), alpha.end());
int i = 25, max_val = alpha.back(), len = tasks.size();
while (i >= 0 && alpha[i] == max_val) --i;
return max(len, (max_val - 1) * (n + 1) + 25 - i);
}
};