-
问题描述
给你一个二维数组 tasks ,用于表示 n 项从 0 到 n - 1 编号的任务。其中 tasks[i] = [enqueueTimei, processingTimei] 意味着第 i 项任务将会于 enqueueTimei 时进入任务队列,需要 processingTimei 的时长完成执行。
现有一个单线程 CPU ,同一时间只能执行 最多一项 任务,该 CPU 将会按照下述方式运行:
如果 CPU 空闲,且任务队列中没有需要执行的任务,则 CPU 保持空闲状态。
如果 CPU 空闲,但任务队列中有需要执行的任务,则 CPU 将会选择 执行时间最短 的任务开始执行。如果多个任务具有同样的最短执行时间,则选择下标最小的任务开始执行。
一旦某项任务开始执行,CPU 在 执行完整个任务 前都不会停止。
CPU 可以在完成一项任务后,立即开始执行一项新任务。
返回 CPU 处理任务的顺序。
示例 1:
输入:tasks = [[1,2],[2,4],[3,2],[4,1]]
输出:[0,2,3,1]
解释:事件按下述流程运行:
- time = 1 ,任务 0 进入任务队列,可执行任务项 = {0}
- 同样在 time = 1 ,空闲状态的 CPU 开始执行任务 0 ,可执行任务项 = {}
- time = 2 ,任务 1 进入任务队列,可执行任务项 = {1}
- time = 3 ,任务 2 进入任务队列,可执行任务项 = {1, 2}
- 同样在 time = 3 ,CPU 完成任务 0 并开始执行队列中用时最短的任务 2 ,可执行任务项 = {1}
- time = 4 ,任务 3 进入任务队列,可执行任务项 = {1, 3}
- time = 5 ,CPU 完成任务 2 并开始执行队列中用时最短的任务 3 ,可执行任务项 = {1}
- time = 6 ,CPU 完成任务 3 并开始执行任务 1 ,可执行任务项 = {}
- time = 10 ,CPU 完成任务 1 并进入空闲状态
示例 2:
输入:tasks = [[7,10],[7,12],[7,5],[7,4],[7,2]]
输出:[4,3,2,0,1]
解释:事件按下述流程运行:
- time = 7 ,所有任务同时进入任务队列,可执行任务项 = {0,1,2,3,4}
- 同样在 time = 7 ,空闲状态的 CPU 开始执行任务 4 ,可执行任务项 = {0,1,2,3}
- time = 9 ,CPU 完成任务 4 并开始执行任务 3 ,可执行任务项 = {0,1,2}
- time = 13 ,CPU 完成任务 3 并开始执行任务 2 ,可执行任务项 = {0,1}
- time = 18 ,CPU 完成任务 2 并开始执行任务 0 ,可执行任务项 = {1}
- time = 28 ,CPU 完成任务 0 并开始执行任务 1 ,可执行任务项 = {}
- time = 40 ,CPU 完成任务 1 并进入空闲状态
提示:
tasks.length == n
1 <= n <= 105
1 <= enqueueTimei, processingTimei <= 109
-
算法分析
本题采用优先队列来解决。主要内容有:1、封装和排序;2、单线程模拟。
每个任务有其进队时间和执行所需的时间,可以用数轴来表示所有任务的分布情况。设置一个变量cur来表示当前时刻,
(1)封装和排序
我们知道所有的任务都有自己的进入任务队列的时刻,但是在题目的输入中,这些任务是不按照这种时间顺序排序的,因此我们要对输入的tasks中元素按照进入时刻来排序,但是每个元素的下标代表着其编号,直接排序后就无从得知每个元素的编号了。
因此,我们可以为“任务”定义一个类Task,其属性除了进队时间et和执行时间pt之外,还有一个编号code,来保存其原来的编号,还要对运算符<根据任务的进队时间et进行重载,以用于排序。
class Task
{
public:
Task(int _code, int _et, int _pt) : code(_code), et(_et), pt(_pt) {}
bool operator<(const Task &_ano) const
{
return this->et < _ano.et;
}
int code, et, pt;
};
定义这么一个类后,我们用一个元素类型为Task得到向量tV,通过一次遍历tasks来保存所有任务,并用sort()进行元素的排序。
/*遍历一遍tasks,每个元素用Task多封装一个属性:编号code*/
for (int i = 0; i < size; i++)
tV.push_back({i, tasks[i][0], tasks[i][1]});
/*依照进队时间先排序*/
sort(tV.begin(), tV.end());
我们已经把所有任务都排好序,并保存了每个任务编号信息。
接下来,为了执行任务,我们可以设置一个变量cur,表示当前时刻,并初始化为tV中的第一个元素的进队时间,只要在这个时刻进队的任务,我们都要把它加入到任务队列。
题目要求:任务队列中有需要执行的任务,则 CPU 将会选择 执行时间最短 的任务开始执行。如果多个任务具有同样的最短执行时间,则选择下标最小的任务开始执行。
我们可以很自然的想到用优先队列来解决问题,不过,我们要对其中元素类型进行定义:
class WaitingTask
{
public:
WaitingTask(int _code, int _pt) : code(_code), pt(_pt) {}
bool operator<(const WaitingTask &_ano) const
{
if (this->pt > _ano.pt)
return true;
else if (this->pt < _ano.pt)
return false;
else
return this->code > _ano.code;
}
int code, pt;
};
其中也对运算符<进行了重载。
然后定义一个优先队列Q(任务队列):
priority_queue<WaitingTask> Q;
这样,Q.top()就是任务队列中执行时间最小的任务(如果有多个相同执行时间的其他任务,则其编号也是这些任务中最小)
(2)单线程模拟
用向量ansV保存CPU处理任务的顺序:
vector<int> ansV; /*答案向量*/
因为tV中元素已经按照进队时间et排好序,遍历过程中,如果某个元素的进队时间小于等于当前时间cur则我们把它加入任务队列Q,然后继续判断下一个元素;如果大于,则Q出队一个元素,表示执行了一个任务,这时候当前时刻cur要加上这个任务的执行时间pt,表示执行完后的时刻,注意如果Q为空,则说明CPU处于空闲状态,要把cur调整为下一个未进队任务的进队时间。重复以上步骤,代码如下:
int cur = tV[0].et, // 当前时间
i = 0;
while (i < size)
{
/*先把当前时间及之前的任务进队*/
while (i < size && tV[i].et <= cur)
{
Q.push({tV[i].code, tV[i].pt});
i++;
}
/*队列非空,先做一个任务。因为任务不会中断,所以把时间调到做完的时刻*/
if (!Q.empty())
{
ansV.push_back(Q.top().code);
cur += Q.top().pt; // 做完这个任务的时刻
Q.pop();
}
else
cur = tV[i].et; /*如果队伍空了,把时间调到下一个任务进队的时间*/
}
最后,如果任务都进队了(即i >= size),而任务队列Q中还有任务没做完,则也要逐一完成:
/*完成队列中剩下的任务*/
while (!Q.empty())
{
ansV.push_back(Q.top().code);
Q.pop();
}
这样,ansV中就保存了完整的CPU处理任务的顺序。
-
完整代码
class Task
{
public:
Task(int _code, int _et, int _pt) : code(_code), et(_et), pt(_pt) {}
bool operator<(const Task &_ano) const
{
return this->et < _ano.et;
}
int code, et, pt;
};
class WaitingTask
{
public:
WaitingTask(int _code, int _pt) : code(_code), pt(_pt) {}
bool operator<(const WaitingTask &_ano) const
{
// priority_queue从小到大用greater,从大到小用less
if (this->pt > _ano.pt)
return true;
else if (this->pt < _ano.pt)
return false;
else
return this->code > _ano.code;
}
int code, pt;
};
class Solution
{
public:
vector<int> getOrder(vector<vector<int>> &tasks)
{
vector<int> ansV; /*答案向量*/
vector<Task> tV;
priority_queue<WaitingTask> Q;
int size = tasks.size();
/*遍历一遍tasks,每个元素用Task多封装一个属性:编号code*/
for (int i = 0; i < size; i++)
tV.push_back({i, tasks[i][0], tasks[i][1]});
/*依照进队时间先排序*/
sort(tV.begin(), tV.end()); // sort从小到大用less, 从大到小用greater
int cur = tV[0].et, // 当前时间
i = 0;
while (i < size)
{
/*先把当前时间及之前的任务进队*/
while (i < size && tV[i].et <= cur)
{
Q.push({tV[i].code, tV[i].pt});
i++;
}
/*队列非空,先做一个任务。因为任务不会中断,所以把时间调到做完的时刻*/
if (!Q.empty())
{
ansV.push_back(Q.top().code);
cur += Q.top().pt; // 做完这个任务的时刻
Q.pop();
}
else
cur = tV[i].et; /*如果队伍空了,把时间调到下一个任务进队的时间*/
}
/*完成队列中剩下的任务*/
while (!Q.empty())
{
ansV.push_back(Q.top().code);
Q.pop();
}
return ansV;
}
} S;