统计出哪个时间段在线人数最多

要统计出哪个时间段在线人数最多,这是一个经典的区间重叠问题,通常使用“扫描线算法”(Sweep Line Algorithm)来解决。算法的核心思想是将用户的登录和登出时间视为事件(登录事件增加在线人数,登出事件减少在线人数),然后按时间顺序扫描这些事件,同时维护一个在线人数计数器,并记录人数达到峰值的时间段。

以下是详细的解决步骤,包括算法描述和伪代码(以 Python 为例)。假设输入是用户登录和登出时间的列表,每个用户有一个登录时间(start)和登出时间(end),时间格式可以是整数(如时间戳)或字符串(如 "HH:MM"),但在处理前需统一转换为可排序的格式(如整数分钟或时间对象)。

算法步骤

  1. 事件处理

    • 为每个用户创建两个事件:
      • 登录事件:(start_time, +1),表示在线人数增加。
      • 登出事件:(end_time, -1),表示在线人数减少。
    • 将所有事件放入一个列表中。
  2. 事件排序

    • 按时间升序排序事件列表。
    • 关键点:如果同一时间有多个事件,需指定处理顺序。通常,先处理登出事件(-1),再处理登录事件(+1),以避免瞬时计数错误(例如,同一时间登出和登录时,登出应先处理,确保计数合理)。排序规则:时间相同时,-1 事件排在 +1 事件之前。
  3. 扫描事件并统计

    • 初始化:
      • 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+1count += 1-1count -= 1
      • 更新 last_time = current_time
    • 注意:第一个事件前(last_timeNone)和最后一个事件后无有效区间,且最后一个事件后人数通常减少,因此无需额外处理。
  4. 合并相邻时间段

    • 扫描后,intervals 存储了所有最大在线人数的时间段,但这些时间段可能相邻(例如 [10:00, 11:00)[11:00, 12:00))。
    • 合并相邻的时间段:遍历 intervals,如果当前时间段的结束时间等于下一个时间段的开始时间,则合并为一个连续时间段(如 [10:00, 11:00)[11:00, 12:00) 合并为 [10:00, 12:00))。
    • 输出 max_count 和合并后的时间段列表。
  5. 时间段表示

    • 时间段定义为左闭右开区间 [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)
  • 事件排序后(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 对象或整数分钟)。

此方法高效且通用,适用于大量用户数据。如果有具体输入或编程语言要求,我可以提供更针对性的代码。

posted @ 2025-08-11 17:47  Arborblog  阅读(19)  评论(0)    收藏  举报