问题描述
第一行输入一个正整数N,代表总共有N个任务。之后共N行,每行包含两个正整数x、y,x代表任务的开始时间,y代表任务的持续时间。输出包含一个整数,代表最高的任务并发数。
问题分析
在这个方案中并没有真的去模拟任务的每一个时间段,而是通过“事件”的方式巧妙地计算了并发数。
我们用事件来表示每个任务的开始和结束,通过在事件发生的时间点上调整并发数,来计算整个任务过程中的最大并发数。
每个任务用两个事件来表示:
- 开始事件:在任务的开始时间,我们增加并发数(+1)。
- 结束事件:在任务的结束时间的下一秒,减少并发数(-1)。这里用“结束时间 + 1”作为结束事件的时间,因为该任务的结束时间不再占用并发。
代码:
int solution(int n, const std::vector<std::vector<int>>& tasks) {
// 定义一个事件列表,存储每个任务的开始和结束事件
std::vector<std::pair<int, int>> events;
for (const auto& task : tasks) {
int start = task[0];
int end = task[0] + task[1] - 1; // 任务结束时间
events.push_back({start, 1}); // 任务开始事件
events.push_back({end + 1, -1}); // 任务结束事件(结束后一天减少并发)
}
// 按时间排序,如果时间相同,优先处理结束事件(-1事件在1事件前)
std::sort(events.begin(), events.end());
int maxConcurrency = 0;
int currentConcurrency = 0;
// 遍历事件,计算最高并发数
for (const auto& event : events) {
currentConcurrency += event.second; // 更新当前并发数
maxConcurrency = std::max(maxConcurrency, currentConcurrency); // 更新最大并发数
}
return maxConcurrency;
}
为什么这种方法有效?
假设任务的时间跨度是 [start, end]:
- 当任务开始时,我们让并发数增加 +1。
- 当任务结束时,我们在 end + 1 的位置让并发数减少 -1。
通过这种方式,我们只在任务的边界处增加或减少并发数。然后通过按照时间顺序遍历这些事件,我们可以在每个关键点(开始或结束时刻)更新当前的并发数,并找到最高的并发数,而不需要遍历所有的时间段。
举个例子
假设有两个任务:
- 任务 1:开始时间 = 1, 持续时间 = 2,时间段为 [1, 2]
- 任务 2:开始时间 = 2, 持续时间 = 3,时间段为 [2, 4]
转换为事件:
- 任务 1 的事件:(1, +1), (3, -1)(开始时间为 1,结束时间为 2,所以在 3 时减少并发)
- 任务 2 的事件:(2, +1), (5, -1)(开始时间为 2,结束时间为 4,所以在 5 时减少并发)
事件列表排序后为:
[(1, +1), (2, +1), (3, -1), (5, -1)]
按顺序处理事件:
- 时间 1:并发数增加 1 → 当前并发数 = 1
- 时间 2:并发数增加 1 → 当前并发数 = 2
- 时间 3:并发数减少 1 → 当前并发数 = 1
- 时间 5:并发数减少 1 → 当前并发数 = 0
在整个过程中,最高的并发数是 2。
这种方法的核心是事件驱动的思想,不需要逐个遍历所有时间点。通过“开始”和“结束”两个关键时刻的事件处理,就能够精确计算出最高并发数。这种方式效率高且简洁,尤其适合大规模任务的并发计算。
总结
这个题目最开始做的时候用的是模拟,容易超时。后来查阅到这种不用模拟的方法,适用于求解并发问题,值得记录一下。