[230603 lc621] 任务调度器

文章介绍了如何解决一个任务调度器问题,其中CPU执行任务需考虑冷却时间。提供了三种方法:模拟法,包括使用vector和优先级队列(堆),以及桶思想。每种方法都详细阐述了其思路和关键点,并给出了相应的代码实现。
摘要由CSDN通过智能技术生成

[230603 lc621] 任务调度器

写在前面:so sad,今天状态很不好,这道题看了一下午加一晚上,屏幕上的字它不进脑子啊!还是深夜效率高!君独何为至于此,山非山兮水非水。

一 题目

621. 任务调度器

给你一个用字符数组 tasks 表示的 CPU 需要执行的任务列表。其中每个字母表示一种不同种类的任务。任务可以以任意顺序执行,并且每个任务都可以在 1 个单位时间内执行完。在任何一个单位时间,CPU 可以完成一个任务,或者处于待命状态。

然而,两个 相同种类 的任务之间必须有长度为整数 n 的冷却时间,因此至少有连续 n 个单位时间内 CPU 在执行不同的任务,或者在待命状态。

你需要计算完成所有任务所需要的 最短时间

示例 1:

输入:tasks = ["A","A","A","B","B","B"], n = 2
输出:8
解释:A -> B -> (待命) -> A -> B -> (待命) -> A -> B
     在本示例中,两个相同类型任务之间必须间隔长度为 n = 2 的冷却时间,而执行一个任务只需要一个单位时间,所以中间出现了(待命)状态。 

示例 2:

输入:tasks = ["A","A","A","B","B","B"], n = 0
输出:6
解释:在这种情况下,任何大小为 6 的排列都可以满足要求,因为 n = 0
["A","A","A","B","B","B"]
["A","B","A","B","A","B"]
["B","B","B","A","A","A"]
...
诸如此类

示例 3:

输入: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

提示:

  • 1 <= task.length <= 104
  • tasks[i] 是大写英文字母
  • n 的取值范围为 [0, 100]

二 模拟法(vector)

2.1 整体思路

模拟法顾名思义,模拟人脑解决此题的流程。对每类任务设置两个属性:可执行时间和剩余次数。可执行时间指该任务冷却后最早可执行的时刻;剩余次数指该任务还需执行几次。

我们使用贪心的思想:利用 time 来记录当前时间,每次选择可执行时间小于等于当前 time 并且剩余次数最多的任务执行,然后更新该任务的可执行时间和剩余次数。

此法中利用 vector 来记录两个属性。

2.2 关键点

易错点在:遍历搜索最多剩余次数的任务时,使用 == 会出现 runtime error,要使用 <=。虽然我不知道为啥但还是记住这样做吧。

2.3 代码分析
class Solution {
public:
    int leastInterval(vector<char>& tasks, int n) {
		//统计频率
        vector<int> freq(26, 0);
        for(char ch: tasks) {
			freq[ch - 'A']++;
        }
        //构造记录两个属性的vector:初始时每类任务的可执行时间均为1
        vector<pair<int, int>> record;
        for(int i = 0; i < freq.size(); ++i) {
			if(freq[0]) {
				record.push_back({1, freq[i]});
            }
        }
        //开始逐时间执行任务
        int time = 0;
        int size = tasks.size();
        for(int i = 0; i < size; ++i) {
			++time;
            //选择当前的最小可执行时间
            int minTime = INT32_MAX;
            for(int j = 0; j < record.size(); ++j) {
				if(record[j].second) {
					minTime = min(minTime, record[j].first);
                }
            }
            //因为最小的可执行时间可能大于当前时间,此时说明会出现空白时间间隔
            time = max(time, minTime); 
            //选择当前可执行时间下剩余次数最多的任务执行
            int index = -1;
            for(int j = 0; j < record.size(); ++j) {
				if(record[j].second && record[j].first <= time) {
					if(index == -1 || record[j].second < record[index].second) {
						index = j;
                    }
                }
            }
            //在该时间片记录第index任务,并更新它的两个属性
            record[index].first = record[index].first + n + 1;
            --record[index].second;
        }
        return time;
    }
};

三 模拟法(优先级队列/堆)

3.1 整体思路

整体思路和使用 vector 的模拟法类似,每次选择可执行时间最小且剩余次数最大的任务优先执行。和上一个做法的不同在于,使用优先级队列来帮助我们排序,每次取堆顶元素执行即可,然后更新该栈顶元素的两个属性,若还需继续执行,则将其压入堆中,直至堆为空,全部任务执行完毕。

3.2 关键点

关键点在优先级队列仿函数的书写,但不太难。

3.3 代码分析
class Solution {
private:
    class Compare{
    public:
        //first为可执行时间,优先可执行时间小的
        //second为剩余次数,在可执行时间相同的情况下,优先剩余次数大的
		bool operator()(pair<int, int> a, pair<int, int> b) {
			if(a.first != b.first) {
				return a.first > b.first;
            }
            return a.second < b.second;
        }  
    };
public:
    int leastInterval(vector<char>& tasks, int n) {
		//首先统计频率
        vector<int> freq(26, 0);
        for(char ch: tasks) {
			freq[ch - 'A']++;
        }
        //定义优先级队列
        priority_queue<pair<int, int>, vector<pair<int, int>>, Compare> que;
        for(int num: freq) {
			if(num) {
				que.push({1, num});
            }
        }
        //开始执行任务
        int time = 0;
        while(!que.empty()) {
			++time;
            pair<int, int> tmp = que.top();
            que.pop();
            time = max(time, tmp.first);
            //执行任务并更新属性
            tmp.first = tmp.first + n + 1;
            tmp.second--;
            if(tmp.second) {
				que.push(tmp);
            }
        }
        return time;
    }
};

四 桶思想

4.1 整体思路

假设最高频率为 q,那么我们设置 q 个桶,并且前 (q - 1) 个桶大小为 (n + 1),因为对于最高频率的任务来说,执行完该任务至少需要 (q - 1) * (n + 1) + 1 的时长。然后我们统计,有多少个频率为 q 的任务,逐个增加最后一个桶的大小。

4.2 关键点

关键点在理解桶的思想,计算最后一个桶的大小。

4.3 代码分析
class Solution {
private:
	static bool compare(int& a, int& b) {
		return a > b;
    }
public:
    int leastInterval(vector<char>& tasks, int n) {
		vector<int> freq(26, 0);
        for(char ch: tasks) {
			++freq[ch - 'A'];
        }
        sort(freq.begin(), freq.end(), compare);
        int lastSize = 1;
        for(int i = 1; i < freq.size(); ++i) {
			if(freq[i] == freq[0]) {
				++lastSize;
            } else {
                break;
            }
        }
        return max(int(tasks.size()), (freq[0] - 1) * (n + 1) + lastSize);
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值