【C++】LeetCode 题库 1834. 单线程 CPU

原题链接

  • 问题描述

给你一个二维数组 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;

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值