代码改变世界

[LeetCode 621] Task Scheduler

2017-11-13 00:52  naturesound  阅读(403)  评论(0编辑  收藏  举报

 这题虽是M难度,却花了我比Hard难度还多的时间。

1.刚开始是想用一个堆排序来实现,排序的比较标准是两个:1.任务的最早启动时间;2.任务的重复次数。并且把1作为第一比较。

后来发现这个思路有误:只要A,B的最早启动时间都小于当前时间,不管A和B哪个最早启动时间更小,这时只需要考虑重复次数:哪个重复次数多就优先。

所以,这里不能简单地只用一个堆排序来实现。实际可增加另一个暂存队列,用于存储刚执行过的任务,原来的优先队列实际存储所有目前就可以执行的任务。

初始化时,所有的任务都压入优先队列,因为刚开始所有的任务都可以执行。但当一个任务执行过之后,则修改其最早启动时间,并压入暂存队列中。

每次循环时,都先将暂存队列中所有启动时间小于等于当前时间的任务压入优先队列即可。

【时间复杂度】max(O(n), O(mlgm)),这里n是完成所有任务所需的总时间长度; 【空间复杂度】O(m), m是任务种类数,此题定为26即可;

 1 struct unit{
 2     char name;
 3     int size;
 4     int start;
 5     unit(char x, int y, int z):name(x),size(y),start(z){};
 6 };
 7 struct cmp{
 8     bool operator()(unit a, unit b){
 9        return a.size <= b.size;
10     }
11 };
12 class Solution {
13 public:
14     priority_queue<unit,vector<unit>, cmp> q;
15     queue<unit> q2;
16     int leastInterval(vector<char>& tasks, int n) {
17         vector<int> t_count(26,0);
18         for(auto a:tasks) t_count[a-'A']++;
19         for(int i=0; i<26; i++){
20             if(t_count[i] > 0) {
21                 q.push(unit(i+'A', t_count[i], 1));
22             }
23         }
24         int res = 0;
25         while(!q.empty() || !q2.empty()){
26             res++;
27             while(!q2.empty() && q2.front().start <= res){
28                 q.push(q2.front());
29                 q2.pop();
30             }
31             if(q.empty()) continue;
32             unit cur = q.top();
33             q.pop();
34             if(--cur.size == 0) continue;
35             cur.start = res + n + 1;
36             q2.push(cur);
37         }
38         return res;
39     }
40 };
需要保存启动时间

1.1 以上算法可进一步优化为O(m)时间复杂度,并且堆中不用保存启动时间:关键点是分批执行。

1)每批从优先队列中执行前面的n+1个任务,每个任务执行后自减1,如果依然非零则存入暂存数组,同时用一个计数器cnt记录这一批实际执行的任务数(因为有可能队列中的任务数已经不足n+1个)。

2)再将暂存数组中的所有任务入队列。

3)如果此时队列不为空(说明要么原来的队列中有大于n+1个任务;要么原来的队列已空,但在暂存数组中还有未完成的任务),此时res需增加n+1

      如果此时队列为空,则说明无后续间隔的要求,res增加cnt即可。

【时间复杂度】O(mlgm)    【空间复杂度】O(m)

 1 class Solution {
 2 public:
 3     int leastInterval(vector<char>& tasks, int n) {
 4         unordered_map<char,int> mp;
 5         for(auto a:tasks) mp[a]++;
 6         priority_queue<int> q;
 7         for(auto a:mp) q.push(a.second);
 8         int res=0;
 9         while(!q.empty()){
10             int cnt = 0;
11             vector<int> t;
12             for(int i=0; i<n+1; i++){
13                 if(!q.empty()){
14                     t.push_back(q.top());
15                     q.pop();
16                     cnt++;
17                 }else break;
18             }
19             for(int i=0; i<t.size(); i++){
20                 if(--t[i] > 0) q.push(t[i]);
21             }
22             res += q.empty() ? cnt:n+1;
23         }
24         return res;
25     }
26 };
不用保存启动时间

 

2.讨论区的最优解法:

一个比较巧的greedy思路:先找出出现频率最高的任务作为“框架”,再往里面“填”其它任务。仔细想想这个过程就会发现,只会出现两种情况:一种是可以填满所有空隙,这时所需的总时间就是所有任务数;一种是空隙无法填满,这时总时间长度就是“框架”的长度。

【时间复杂度】O(m), 这里m是任务种类数,同上;【空间复杂度】O(m),同上。

框架法
 1   public int leastInterval(char[] tasks, int n) {
 2 
 3         int[] c = new int[26];
 4         for(char t : tasks){
 5             c[t - 'A']++;
 6         }
 7         Arrays.sort(c);
 8         int i = 25;
 9         while(i >= 0 && c[i] == c[25]) i--;
10 
11         return Math.max(tasks.length, (c[25] - 1) * (n + 1) + 25 - i);
12     }