题目重点:输出开始和结束时间,堆只能排序目前已收到的完成事件。
题意与假设
这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。
- requestID唯一且可比较,begin/end时间由调用者提供,可乱序完成但每请求end≥begin。
- take_completed只取当前完成集中end最早者;全局时间排序若未来还能迟到旧事件,需要水位线或等待流结束。
解题路线
- active保存开始时间,end查找并删active,将(end,id,start)压最小堆。done集拒绝ID再次使用;若生产ID复用需版本。
- take返回堆顶,平局按ID确定。未知end、重复begin和时间倒置都拒绝;异常前不修改有效active状态。
- 全局日志“按end永远递增”与在线即时输出不能在无限乱序下同时保证。可在watermark之前释放完成日志,或离线收全后排序。等待中的请求不能占完成堆。
手动走一遍
A开始1结束9,B开始2结束6,均收到完成后先输出B(2,6),再A(1,9)。已输出end9后收到end5,当前集合同仍可返回5,但它不是全局有序流。
Python 参考实现
下面的代码按上文假设实现,适合本地运行并追加自己的测试用例。
代码区可横向滚动;使用键盘时,先聚焦代码区再按方向键查看。
from heapq import heappush,heappop
class Requests:
def __init__(self):self.active={};self.done=set();self.heap=[]
def begin(self,key,time):
if key in self.active or key in self.done:raise ValueError("duplicate request")
self.active[key]=time
def end(self,key,time):
if key not in self.active:raise ValueError("not active")
start=self.active[key]
if time<start:raise ValueError("end precedes start")
del self.active[key];self.done.add(key);heappush(self.heap,(time,key,start))
def take_completed(self):
# Earliest end among completions currently received, not all future events.
if not self.heap:return None
end,key,start=heappop(self.heap);return key,start,end
复杂度
begin期望O(1),end/take O(logC),C待输出完成数;active/done/heap空间O(N),done永久保留需生命周期清理。
易错点与追问
容易失分的边界
- heap以start做键会按错目标。
- 拿处理时机器now代替业务事件timestamp会掩盖迟到语义。
继续深挖
- 多线程begin/end如何防同ID双重完成?
- 日志落盘失败如何定义重试幂等与确认时机?
