要统计出哪个时间段在线人数最多,这是一个经典的区间重叠问题,通常使用“扫描线算法”(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 示例)
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) 内人数恒定为 countif count > max_count:max_count = countintervals = [(last_time, time)] # 重置为新时间段elif count == max_count:intervals.append((last_time, time)) # 添加新时间段# 更新计数count += deltalast_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)
事件排序后:(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。
合并时间段:[10:00, 11:00) 和 [11:00, 12:00) 合并为 [10:00, 12:00)。
输出:最大在线人数为 2,时间段为 [10:00, 12:00)。
边界情况处理
无效输入:如果登出时间在登录时间前,计数可能为负。算法可处理,但建议输入校验。
同时事件:排序规则确保同一时间登出先处理,避免计数虚高。
无用户在线:如果所有时间段不重叠,最大人数可能为 1 或 0。
时间格式:实际代码中,时间应统一为可排序类型(如 datetime 对象或整数分钟)。
此方法高效且通用,适用于大量用户数据。如果有具体输入或编程语言要求,我可以提供更针对性的代码。
