2589-完成所有任务的最少时间
你有一台电脑,它可以 同时 运行无数个任务。给你一个二维整数数组 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
对于 tasks}[i] 来说,它右侧的任务要么和它没有交集,要么包含它的区间后缀。
提示 3
遍历排序后的任务,先统计区间内的已有的电脑运行时间点,如果个数小于 duration,则需要新增时间点。根据提示 2,尽量把新增的时间点安排在区间 [\textit{start},\textit{end}] 的后缀上,这样下一个区间就能统计到更多已有的时间点。
附:视频讲解
1 | class Solution: |
1 | class Solution { |
1 | class Solution { |
1 | func findMinimumTime(tasks [][]int) (ans int) { |
复杂度分析
- 时间复杂度:O(nU),其中 n 为 nums 的长度,U=\max(\textit{end}_i)。
- 空间复杂度:O(U)。
方法二:贪心+线段树优化
在方法一的暴力更新上优化。
由于有区间更新操作,需要用 lazy 线段树,之前在 双周赛 98 中讲过了。
对于本题,在更新的时候需要优先递归右子树,从而保证是从右往左更新。其余细节见代码注释。
注:由于线段树常数比较大,在数据范围只有几百几千的情况下,不一定比方法一的暴力快。
1 | class Solution: |
1 | class Solution { |
1 | class Solution { |
1 | type seg []struct { |
复杂度分析
- 时间复杂度:O(n\log U),其中 n 为 nums 的长度,U=\max(\textit{end}_i)。
- 空间复杂度:O(U)。
注:如果改用动态开点线段树,可以做到 O(n\log n) 时间和 O(n\log n) 空间。
方法三:贪心+栈优化+二分查找
前置知识:二分查找
见【基础算法精讲 04】 。
思路
由于每次都是从右到左新增时间点,相当于把若干右侧的区间合并成一个大区间,因此可以用栈来优化。
栈中保存闭区间的左右端点,以及从栈底到栈顶的区间长度之和(类似前缀和)。
合并前先在栈中二分查找左端点所在区间,由于我们保存了长度之和,所以可以算出 [\textit{start},\textit{end}] 范围内的运行中的时间点。
如果还需要新增时间点,那么就从右到左合并,具体细节见代码。
1 | class Solution: |
1 | class Solution { |
1 | class Solution { |
1 | func findMinimumTime(tasks [][]int) int { |
复杂度分析
- 时间复杂度:O(n\log n),其中 n 为 nums 的长度。
- 空间复杂度:O(n)。