你有一台电脑,它可以 同时 运行无数个任务。给你一个二维整数数组 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;
// }
// };