2589. 完成所有任务的最少时间 Hard

你有一台电脑,它可以 同时 运行无数个任务。给你一个二维整数数组 tasks ,其中 tasks[i] = [starti, endi, durationi] 表示第 i 个任务需要在 闭区间 时间段 [starti, endi] 内运行 durationi 个整数时间点(但不需要连续)。

当电脑需要运行任务时,你可以打开电脑,如果空闲时,你可以将电脑关闭。

请你返回完成所有任务的情况下,电脑最少需要运行多少秒。

示例 1:

输入:tasks = [[2,3,1],[4,5,1],[1,5,2]]
输出:2
解释:
- 第一个任务在闭区间 [2, 2] 运行。
- 第二个任务在闭区间 [5, 5] 运行。
- 第三个任务在闭区间 [2, 2] 和 [5, 5] 运行。
电脑总共运行 2 个整数时间点。

示例 2:

输入:tasks = [[1,3,2],[2,5,3],[5,6,2]]
输出:4
解释:
- 第一个任务在闭区间 [2, 3] 运行
- 第二个任务在闭区间 [2, 3] 和 [5, 5] 运行。
- 第三个任务在闭区间 [5, 6] 运行。
电脑总共运行 4 个整数时间点。

提示:

 ·1 <= tasks.length <= 2000

 ·tasks[i].length == 3

 ·1 <= starti, endi <= 2000

 ·1 <= durationi <= endi - starti + 1

题目大意:在可以用同一时间完成多个任务的情况下,计算完成所有任务的最短时间。

分析:

(1)当一个时间段与之前的时间段重合时,就可以尝试用重合的时间段完成该时间段内的任务,但当重合时间段用完而该时间段中的任务还未执行完时,为了使得该时间段中的任务能够尽量与之后时间段中的任务重合,因此最好将剩余的任务放在该时间段末尾执行,此处运用了贪心算法的思想;

(2)由(1)设计算法,用数组time记录需要运行的时间点,即time[i]=1表示第i个时间点需要运行任务,然后将tasks按结束时间升序排序,接下来对该数组中得时间段进行遍历,首先根据数组time计算tasks[i]中的任务是否可被之前重合的时间段执行完,即计算tasks[i][2]-accumulate(time.begin()+tasks[i][0],time.begin()+tasks[i][1]+1,0)是否小于等于0。若小于等于0,则tasks[i]可与之前的任务完全重复执行,可忽略tasks[i];若>0,则需要将剩余的tasks[i][2]放在当前时间段的末尾执行,即在time数组中将当前时间段的末尾tasks[i][2]个值为0的元素置为1。

思路优化:

(1)用数组time记录需要运行的时间点,当最大时间点较大时,时间复杂度较高,因此可以用数组Time记录需要运行的时间段,降低时间复杂度,设数组元素为(startRunTime,endRunTime);

(2)由(1)可知,此时需查找与当前时间段所有有时间重合的运行片段,即满足tasks[i]>=startRunTime的所有运行时间段,由于运行片段之间是不重合的,且startRunTime有序递增,因此可用二分查找加速查找首个满足条件的时间段;

(3)由于找到首个满足条件的时间段(假设其在Time中的下标为k)后,需要计算接下来所有满足条件的运行时间段的总时间T_i,因此可采用前缀和加速计算,即将数组元素重定义为(startRunTime,endRunTime,sumDuration),其中sumDuration表示到时间点endRunTime为止,所有运行时间段的总时间,则T_i=Time.back().sumDuration-Time[k].sumDuration+(Time[k].endRunTIme>=tasks[i][0]? Time[k].endRunTime-tasks[i][0]+1:0)。由上述算法得到T_i后计算tasks[i][2]-T_i的结果duration,若duration大于0,需根据其值大小判断是否需要与前面的片段或者直接插入新片段,若小于等于0,则当前片段的任务可与之前的任务重复完成,不予处理。

class RunTime{
public:
    RunTime(int start,int end,int sumDuration)
    :_start(start)
    ,_end(end)
    ,_sumDuration(sumDuration)
    {}
    int _start;
    int _end;
    int _sumDuration;//截至end时间点,所有时间段的总运行时间
};
class Solution {
public:
    int findMinimumTime(vector<vector<int>>& tasks) {
        int start,end,duration;
        sort(tasks.begin(),tasks.end(),[](const vector<int>& task1,const vector<int>& task2){
            return task1[1]<task2[1];
        });
        vector<RunTime> s;//存储需要运行的时间段的栈 
        s.reserve(500);
        s.emplace_back(-1,-1,0);
        for(auto& task:tasks){
            start=task[0];
            end=task[1];
            duration=task[2];
            //二分查找与当前时间段第一个相重合的时间段
            int l=0,r=s.size()-1,mid;
            while(l<=r){
                mid=(l+r)/2;
                if(start<s[mid]._start) --r;
                else ++l;
            }
            //r所在位置即为查找目标
            duration-=s.back()._sumDuration-s[r]._sumDuration;
            if(start<=s[r]._end) duration-=s[r]._end-start+1;
            if(duration<=0) continue;
            //若先前的时间无法运行完该时间段内的任务,需增加新的运行时间段
            while(end-s.back()._end<=duration){
                duration+=s.back()._end-s.back()._start+1;//合并区间
                s.pop_back();//弹出区间
            }
            s.emplace_back(end-duration+1,end,duration+s.back()._sumDuration);
        }
        return s.back()._sumDuration;
    }
};
//优化前的代码
// class Solution {
// public:
//     int findMinimumTime(vector<vector<int>>& tasks) {
//         int N=tasks.size(),ans=0;
//         sort(tasks.begin(),tasks.end(),[](const vector<int>& task1,const vector<int>& task2){
//             return task1[1]<task2[1];
//         });
//         vector<int> time(tasks[N-1][1]+1,0);
//         for(int i=0;i<N;++i){
//             int start=tasks[i][0],end=tasks[i][1],duration=tasks[i][2];
//             duration-=accumulate(time.begin()+start,time.begin()+end+1,0);
//             ans+=max(0,duration);
//             for(int j=end;j>=0&&duration>0;--j){
//                 if(!time[j]){
//                     time[j]=1;
//                     --duration;
//                 }
//             }
//         }
//         return ans;
//     }
// };

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值