AI刷题106题 | 豆包Marscode AI刷题

81 阅读3分钟

问题描述

第一行输入一个正整数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. 当任务开始时,我们让并发数增加 +1。
  2. 当任务结束时,我们在 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 → 当前并发数 = 1
  2. 时间 2:并发数增加 1 → 当前并发数 = 2
  3. 时间 3:并发数减少 1 → 当前并发数 = 1
  4. 时间 5:并发数减少 1 → 当前并发数 = 0

在整个过程中,最高的并发数是 2。

这种方法的核心是事件驱动的思想,不需要逐个遍历所有时间点。通过“开始”和“结束”两个关键时刻的事件处理,就能够精确计算出最高并发数。这种方式效率高且简洁,尤其适合大规模任务的并发计算。

总结

这个题目最开始做的时候用的是模拟,容易超时。后来查阅到这种不用模拟的方法,适用于求解并发问题,值得记录一下。