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 <= 2000tasks[i].length == 31 <= starti, endi <= 20001 <= durationi <= endi - starti + 1
解题思路
这是一个贪心算法问题,关键思路是尽可能晚地安排任务执行,这样可以最大化时间重叠利用。
核心策略:
按结束时间排序:将所有任务按照结束时间升序排列,这样我们可以从最早结束的任务开始处理。
贪心选择时间点:对于每个任务,我们应该尽可能在其时间窗口的末尾安排执行时间。原因是越晚安排,越有可能与后续任务的时间窗口重叠,从而节省总开机时间。
动态维护已开机时间:使用一个布尔数组记录每个时间点是否已经开机。对于当前任务,先统计在其时间窗口内已经有多少时间点开机,然后从右到左(从结束时间往开始时间)贪心地选择新的时间点开机,直到满足该任务的持续时间要求。
算法流程:
- 将任务按结束时间排序
- 维护一个
running数组表示每个时间点是否开机 - 对每个任务,计算已满足的时间,然后从右到左补充不足的时间
- 最后统计总开机时间
时间复杂度为 O(n²),其中 n 是最大时间值,空间复杂度 O(n)。
代码实现
class Solution {
public:
int findMinimumTime(vector<vector<int>>& tasks) {
sort(tasks.begin(), tasks.end(), [](const vector<int>& a, const vector<int>& b) {
return a[1] < b[1];
});
vector<bool> running(2001, false);
for (auto& task : tasks) {
int start = task[0], end = task[1], duration = task[2];
// Count already satisfied time
int satisfied = 0;
for (int i = start; i <= end; i++) {
if (running[i]) satisfied++;
}
// Need to schedule more time
int need = duration - satisfied;
// Greedily choose time slots from right to left
for (int i = end; i >= start && need > 0; i--) {
if (!running[i]) {
running[i] = true;
need--;
}
}
}
int result = 0;
for (bool run : running) {
if (run) result++;
}
return result;
}
};
class Solution:
def findMinimumTime(self, tasks: List[List[int]]) -> int:
tasks.sort(key=lambda x: x[1])
running = [False] * 2001
for start, end, duration in tasks:
# Count already satisfied time
satisfied = sum(running[start:end+1])
# Need to schedule more time
need = duration - satisfied
# Greedily choose time slots from right to left
for i in range(end, start - 1, -1):
if need <= 0:
break
if not running[i]:
running[i] = True
need -= 1
return sum(running)
public class Solution {
public int FindMinimumTime(int[][] tasks) {
Array.Sort(tasks, (a, b) => a[1].CompareTo(b[1]));
bool[] running = new bool[2001];
foreach (var task in tasks) {
int start = task[0], end = task[1], duration = task[2];
// Count already satisfied time
int satisfied = 0;
for (int i = start; i <= end; i++) {
if (running[i]) satisfied++;
}
// Need to schedule more time
int need = duration - satisfied;
// Greedily choose time slots from right to left
for (int i = end; i >= start && need > 0; i--) {
if (!running[i]) {
running[i] = true;
need--;
}
}
}
int result = 0;
foreach (bool run in running) {
if (run) result++;
}
return result;
}
}
var findMinimumTime = function(tasks) {
tasks.sort((a, b) => a[1] - b[1]);
const running = new Array(2001).fill(false);
for (const [start, end, duration] of tasks) {
// Count already satisfied time
let satisfied = 0;
for (let i = start; i <= end; i++) {
if (running[i]) satisfied++;
}
// Need to schedule more time
let need = duration - satisfied;
// Greedily choose time slots from right to left
for (let i = end; i >= start && need > 0; i--) {
if (!running[i]) {
running[i] = true;
need--;
}
}
}
return running.filter(run => run).length;
};
复杂度分析
| 复杂度类型 | 值 | 说明 |
|---|---|---|
| 时间复杂度 | O(n × m + n log n) | n 是任务数量,m 是最大时间值(2000),排序 O(n log n),每个任务遍历时间区间 O(m) |
| 空间复杂度 | O(m) | 需要 O(m) 的数组记录每个时间点的状态,m = 2000 |
相关题目
- . Single-Threaded CPU (Medium)