
1) 【一句话结论】
采用滑动窗口(双端队列维护时间戳)结合哈希表统计活跃用户,再用最大堆维护Top N,时间复杂度O(N + K log K),空间复杂度O(N+K),适用于大数据量或流式数据实时更新场景。
2) 【原理/概念讲解】
老师:“要高效计算7天活跃用户并取Top N,核心是处理时间窗口的动态变化。首先,时间窗口是最近7天,需维护一个大小为7天的滑动窗口。用**双端队列(deque)存储每个用户最近一次登录的时间戳,当新记录时间超过窗口(即超过当前时间-7天)时,移除队列头部(最早的时间戳),并从哈希表删除该用户,避免旧数据残留。哈希表用于记录每个用户是否在窗口内登录过(只要有一个记录就算1次,值设为1即可)。对于Top N,用最大堆(优先队列)**维护,每次新用户进入窗口时,若堆大小小于N则直接加入;若等于N且新用户时间戳比堆顶(当前Top N中最小的时间戳)大,则替换堆顶。这样能实时维护Top N。类比的话,双端队列就像一个滑动的时间窗口,只保留最近7天的记录,哈希表快速判断用户是否活跃,最大堆则高效维护Top N。”
3) 【对比与适用场景】
| 方法 | 定义 | 特性 | 使用场景 | 注意点 |
|---|---|---|---|---|
| 滑动窗口+哈希表+堆(流式) | 维护时间戳双端队列,增量更新哈希表,用最大堆维护Top N | 时间复杂度O(N + K log K),空间O(N+K),支持实时更新 | 大数据量、流式数据、需要动态维护Top N | 需要额外数据结构(双端队列、堆),实现复杂度较高 |
| 全量扫描+哈希表+排序(静态) | 一次性过滤所有日志,统计活跃后排序取Top N | 时间复杂度O(N + M log M),空间O(M) | 数据量小、静态数据、不需要实时更新 | 排序耗时,若M远小于N,效率尚可 |
| 哈希表+排序(简单过滤) | 过滤时间窗口后直接排序用户ID取Top N | 时间复杂度O(N + M log M),空间O(M) | 小规模数据,简单场景 | 未考虑实时更新,数据量大时效率低 |
4) 【示例】
假设日志数据(用户ID, 时间戳,单位ms):
伪代码(简化):
import heapq
from collections import deque
def get_top_active_users(logs, now, N):
window_start = now - 7 * 24 * 60 * 60 * 1000 # 7天毫秒数
dq = deque() # 存储时间戳
active = {} # 用户是否活跃
topk = [] # 最大堆,存储 (时间戳, 用户ID)
for user, ts in logs:
if ts < window_start:
continue
# 移除超出窗口的旧时间戳
while dq and dq[0] < ts - window_start:
old_ts = dq.popleft()
if active.get(old_ts) is not None:
del active[old_ts]
dq.append(ts)
active[ts] = user
# 更新Top N
if len(topk) < N:
heapq.heappush(topk, (ts, user))
else:
if ts > topk[0][0]:
heapq.heapreplace(topk, (ts, user))
top_users = [user for _, user in sorted(topk, reverse=True)]
return top_users
5) 【面试口播版答案】
“首先,这个问题要计算最近7天活跃用户并找出Top N。核心思路是采用滑动窗口结合哈希表和最大堆,实现高效动态更新。步骤是:1. 定义时间窗口(当前时间减7天);2. 用双端队列维护每个用户最近一次登录的时间戳,当新记录时间超过窗口时,移除队列头部(旧时间戳),并从哈希表删除该用户,避免旧数据干扰;3. 用哈希表记录每个用户是否在窗口内登录过(只要有一个记录就算1次);4. 用最大堆维护Top N,每次新用户进入窗口时,若堆大小小于N则加入,若等于N且新用户时间戳比堆顶大,则替换堆顶,这样能实时保持Top N。时间复杂度方面,遍历日志是O(N),维护双端队列和堆是O(1)每次,总时间O(N + K log K),空间复杂度是O(N+K),其中N是日志数量,K是Top N数量。这种方法适用于大数据量或流式数据,能高效处理实时更新。”
6) 【追问清单】
7) 【常见坑/雷区】