一 、题目分析
该题目要求我们计算一个多任务下载器在一段时间内的最大并发数,也就是同时有多少个任务在下载。每个任务包括开始时间和持续时间两个要素,我们需要通过这些信息计算在某一时刻最多有多少个任务是并行下载的。
给定一个二维数组,数组的每个元素分别表示一个任务的开始时间和持续时间,例如 [x, y] 表示任务在第x秒开始,并持续y秒。我们需要找出最大并发数,即在同一时刻,下载任务的数量最多是多少。
二 、解题思路
在这个问题中,我们可以利用 事件驱动 的方式来解决问题。将任务的开始和结束看作事件,统计每个时刻发生的“开始”和“结束”事件。通过对事件的排序和遍历,我们可以计算出最大并发数。
- 事件表示: 每个任务都会产生两个事件:一个是任务开始的事件,另一个是任务结束的事件。
- 任务的开始时间是一个“开始”事件;
- 任务的结束时间是一个“结束”事件(任务的结束时间可以用开始时间加上持续时间得出)。
- 事件排序: 为了计算并发数,我们需要按照时间顺序处理事件。
- 任务的开始时间和结束时间有可能相同,为了避免同一时刻开始和结束任务的顺序混乱,我们规定“结束”事件应当排在“开始”事件之后(使用sort函数进行排序)。
- 遍历事件: 通过遍历所有的事件,记录当前的并发数(startCount):
- 每遇到一个“开始”事件,当前并发数加一;
- 每遇到一个“结束”事件,当前并发数减一;
- 在每一步遍历过程中,计算出当前的并发数的最大值,即为所求的最大并发数。
三、解决方案
- 构建事件列表: 对于每个任务,生成两个事件:
(starttime, 'start')表示任务开始;(endtime, 'end')表示任务结束。
- 排序事件: 对事件进行排序,首先按时间升序排列。如果时间相同,则结束事件应排在开始事件之后。
- 遍历事件并计算最大并发数: 通过遍历事件并更新当前并发数,得到最大并发数。
四、代码实现
let arr = []
for (let k of array) {
let start = k[0]
let end = k[0] + k[1]
arr.push([start, 'start'])
arr.push([end, 'end'])
}
// 对arr数组进行排序
arr.sort((a, b) => {
if (a[0] === b[0]) {
//如果时间相同,则end排在start之后
return a[0] === 'start' ? 1 : -1
}
return a[0] - b[0]
})
let startCount = 0
let maxCount = 0
for (let k of arr) {
if (k[1] === 'start') {
startCount += 1
maxCount = Math.max(startCount, maxCount)
} else {
startCount -= 1
}
}
return maxCount
五、关键点解析及知识点补充
- 时间复杂度:
-
生成事件列表的时间复杂度是 O(n),其中 n 是任务的数量。
-
排序事件的时间复杂度是 O(2n log(2n)),即 O(n log n)。
-
遍历事件的时间复杂度是 O(n),因为每个任务会生成两个事件,总共 2n 个事件。
因此,整体时间复杂度为 O(n log n),这是一个非常高效的算法,适用于较大的输入规模。
- 空间复杂度:
- 事件列表占用的空间是 O(2n),因为每个任务会产生两个事件。
- 额外的空间开销是常数级别的,因此空间复杂度是 O(n)。
- 为什么要让结束事件排在开始事件之后?
- 这是一种特定的排序规则,确保在同一时刻,如果有任务结束和任务开始,任务的并发数在计算时是准确的。若不这样做,可能会导致计数错误。
- 边界情况:
- 当所有任务的时间都不重叠时,最大并发数应为 1。
- 如果任务完全重叠(例如任务开始和结束的时间完全相同),最大并发数是任务的数量。
- sort函数(排序):
在 js 中,
Array.prototype.sort()方法会按照返回值的不同来决定两个元素的排序顺序。sort()接受一个比较函数作为参数,比较函数的返回值决定了排序的顺序(例如,sort(a,b)=>a-b):
- 如果返回值小于 0,则
a会排在b前面。 - 如果返回值大于 0,则
b会排在a前面。 - 如果返回值等于 0,则
a和b的顺序不变。
六、总结
通过将任务的开始时间和结束时间转换为事件,并根据时间对这些事件进行排序,我们能够有效地计算出在任何时刻的任务并发数。使用这种事件驱动的方式,能够高效地解决此类问题。