统计出哪个时间段在线人数最多
要统计出哪个时间段在线人数最多,这是一个经典的区间重叠问题,通常使用“扫描线算法”(Sweep Line Algorithm)来解决。算法的核心思想是将用户的登录和登出时间视为事件(登录事件增加在线人数,登出事件减少在线人数),然后按时间顺序扫描这些事件,同时维护一个在线人数计数器,并记录人数达到峰值的时间段。
以下是详细的解决步骤,包括算法描述和伪代码(以 Python 为例)。假设输入是用户登录和登出时间的列表,每个用户有一个登录时间(start)和登出时间(end),时间格式可以是整数(如时间戳)或字符串(如 "HH:MM"),但在处理前需统一转换为可排序的格式(如整数分钟或时间对象)。
算法步骤
-
事件处理:
- 为每个用户创建两个事件:
- 登录事件:
(start_time, +1)
,表示在线人数增加。 - 登出事件:
(end_time, -1)
,表示在线人数减少。
- 登录事件:
- 将所有事件放入一个列表中。
- 为每个用户创建两个事件:
-
事件排序:
- 按时间升序排序事件列表。
- 关键点:如果同一时间有多个事件,需指定处理顺序。通常,先处理登出事件(-1),再处理登录事件(+1),以避免瞬时计数错误(例如,同一时间登出和登录时,登出应先处理,确保计数合理)。排序规则:时间相同时,
-1
事件排在+1
事件之前。
-
扫描事件并统计:
- 初始化:
count = 0
:当前在线人数。max_count = 0
:最大在线人数。last_time = None
:上一个事件的时间,用于定义时间段。intervals = []
:存储所有最大人数的时间段(每个时间段是元组(start, end)
)。
- 遍历排序后的事件列表:
- 如果
last_time
不为None
且与当前事件时间不同(即存在有效时间段),则检查[last_time, current_time)
区间:- 该区间内在线人数恒定为
count
。 - 如果
count > max_count
,更新max_count = count
,并重置intervals = [(last_time, current_time)]
。 - 如果
count == max_count
,添加(last_time, current_time)
到intervals
。
- 该区间内在线人数恒定为
- 根据事件类型更新
count
:+1
则count += 1
,-1
则count -= 1
。 - 更新
last_time = current_time
。
- 如果
- 注意:第一个事件前(
last_time
为None
)和最后一个事件后无有效区间,且最后一个事件后人数通常减少,因此无需额外处理。
- 初始化:
-
合并相邻时间段:
- 扫描后,
intervals
存储了所有最大在线人数的时间段,但这些时间段可能相邻(例如[10:00, 11:00)
和[11:00, 12:00)
)。 - 合并相邻的时间段:遍历
intervals
,如果当前时间段的结束时间等于下一个时间段的开始时间,则合并为一个连续时间段(如[10:00, 11:00)
和[11:00, 12:00)
合并为[10:00, 12:00)
)。 - 输出
max_count
和合并后的时间段列表。
- 扫描后,
-
时间段表示:
- 时间段定义为左闭右开区间
[start, end)
,表示从start
时间点(包含)开始,到end
时间点(不包含)结束。例如,[10:00, 11:00)
包括 10:00 到 10:59.999,但不包括 11:00。 - 在输出时,可根据需要格式化为字符串(如 "10:00-11:00")。
- 时间段定义为左闭右开区间
时间复杂度
- 排序事件:O(n log n),其中 n 是事件数量(每个用户2个事件,n = 2 * 用户数)。
- 扫描事件:O(n)。
- 合并时间段:O(m),其中 m 是最大人数时间段的个数(通常很小)。
- 总体复杂度:O(n log n),主要由排序决定。
伪代码(Python 示例)
def find_peak_online_times(user_intervals):
# user_intervals 是列表,每个元素为 (start, end) 表示一个用户的在线时间段
events = []
for start, end in user_intervals:
events.append((start, 1)) # 登录事件,+1
events.append((end, -1)) # 登出事件,-1
# 排序事件:按时间升序,同一时间时 -1 事件在 +1 事件前
events.sort(key=lambda x: (x[0], x[1]))
count = 0 # 当前在线人数
max_count = 0 # 最大在线人数
last_time = None # 上一个事件时间
intervals = [] # 存储最大人数的时间段
# 扫描事件
for time, delta in events:
# 检查上一个事件到当前事件之间的区间
if last_time is not None and last_time < time:
# 区间 [last_time, time) 内人数恒定为 count
if count > max_count:
max_count = count
intervals = [(last_time, time)] # 重置为新时间段
elif count == max_count:
intervals.append((last_time, time)) # 添加新时间段
# 更新计数
count += delta
last_time = time
# 合并相邻时间段
if not intervals:
return max_count, []
merged_intervals = []
current_start, current_end = intervals[0]
for i in range(1, len(intervals)):
if intervals[i][0] == current_end: # 相邻:当前结束 == 下一开始
current_end = intervals[i][1]
else:
merged_intervals.append((current_start, current_end))
current_start, current_end = intervals[i]
merged_intervals.append((current_start, current_end)) # 添加最后一个
return max_count, merged_intervals
# 示例输入和输出
if __name__ == "__main__":
# 示例:用户在线时间段,时间用整数分钟表示(如 10:00 为 600 分钟)
user_intervals = [
(600, 720), # 用户A: 10:00 登录, 12:00 登出
(660, 780), # 用户B: 11:00 登录, 13:00 登出
(540, 660), # 用户C: 9:00 登录, 11:00 登出
]
max_count, peak_times = find_peak_online_times(user_intervals)
print(f"最大在线人数: {max_count}")
print("时间段:")
for start, end in peak_times:
# 格式化输出(假设时间整数转回 "HH:MM")
start_str = f"{start//60}:{start%60:02d}" # 简化为示例
end_str = f"{end//60}:{end%60:02d}"
print(f"{start_str} 到 {end_str}")
# 输出: 最大在线人数 2, 时间段 10:00 到 12:00
示例解释
- 输入:三个用户的在线时间段:
- 用户A:
[10:00, 12:00)
- 用户B:
[11:00, 13:00)
- 用户C:
[9:00, 11:00)
- 用户A:
- 事件排序后:
(9:00, +1)
,(10:00, +1)
,(11:00, +1)
,(11:00, -1)
,(12:00, -1)
,(13:00, -1)
。注意:11:00 有登录和登出事件,但登出(-1)优先处理。 - 扫描过程:
- 9:00 登录:计数=1,
last_time=9:00
。 - 10:00 登录:区间
[9:00, 10:00)
人数=1(等于当前最大),记录该区间;计数=2,last_time=10:00
。 - 11:00 登出:区间
[10:00, 11:00)
人数=2(大于最大),更新max_count=2
,记录[10:00, 11:00)
;计数=1(处理后),last_time=11:00
。 - 11:00 登录:时间与上事件相同,跳过区间检查;计数=2,
last_time=11:00
。 - 12:00 登出:区间
[11:00, 12:00)
人数=2(等于最大),记录[11:00, 12:00)
;计数=1,last_time=12:00
。 - 13:00 登出:区间
[12:00, 13:00)
人数=1(小于最大),忽略;计数=0。
- 9:00 登录:计数=1,
- 合并时间段:
[10:00, 11:00)
和[11:00, 12:00)
合并为[10:00, 12:00)
。 - 输出:最大在线人数为 2,时间段为
[10:00, 12:00)
。
边界情况处理
- 无效输入:如果登出时间在登录时间前,计数可能为负。算法可处理,但建议输入校验。
- 同时事件:排序规则确保同一时间登出先处理,避免计数虚高。
- 无用户在线:如果所有时间段不重叠,最大人数可能为 1 或 0。
- 时间格式:实际代码中,时间应统一为可排序类型(如 datetime 对象或整数分钟)。
此方法高效且通用,适用于大量用户数据。如果有具体输入或编程语言要求,我可以提供更针对性的代码。