接着扫描线求峰值那篇留的一个问题:算出"最高多少人在线"不难,但"这个最高点一共维持了多久"该怎么定义结束时刻?容易想当然地以为"人数一下降就该结束计时",但真实跑出来的例子证明,只有"刷新了更高纪录"才会让计时清零重来;人数下降只是暂停,回到(不超过)当前最高值还会接着累加。
沿用上一篇用过的例子:A 在线区间 [1,4)、B 在 [2,6)、C 在 [5,7)。峰值人数是 2,但 2 人在线的时间其实分两段:t=2,3 和 t=5,中间 t=4 掉回了 1 人。真实跑排序事件版、差分数组版、暴力按秒计数三种算法交叉验证:
records = [("A", 1, 4), ("B", 2, 6), ("C", 5, 7)]
sweep_peak_duration(records) # 排序事件版
diff_array_peak_duration(records, 8) # 差分数组版
brute_force_peak_duration(records, 8) # 暴力按秒计数
排序事件版: (2, 3)
差分数组版: (2, 3)
暴力验证 : (2, 3)
三种算法结果完全一致,峰值持续时长是 3 秒,不是"从 t=4 人数下降那一刻就截止"算出来的第一段长度 2。t=4 人数掉到 1 时,算法只是暂停累加,并没有清零——t=5 人数又回到 2(峰值本身没被刷新,只是重新达到了),之前累计的 2 秒被原样保留,继续往上加,最终 2+1=3。
构造一个更有代表性的例子:A 全程在线 [1,26),B、C、D 先后短暂上线把人数顶到 2,分三段累计了 1+4+4=9 秒的"2 人在线"记录;直到 E 在 t=17 上线,把人数第一次顶到 3——这一刻峰值刷新,之前辛辛苦苦攒的 9 秒全部作废,持续时长清零重新从 1 开始数:
records = [("A",1,26), ("B",2,6), ("C",10,14), ("D",16,20), ("E",17,19)]
sweep_peak_duration(records)
t= 2 B登录 cur=2 peak=2 结算[1,2)长度1 -> duration=1 (但同一刻创新高2,duration作废清零) t= 6 B登出 cur=1 peak=2 结算[2,6)长度4 -> duration=4 t=10 C登录 cur=2 peak=2 回到峰值,不创新高,不清零 t=14 C登出 cur=1 peak=2 结算[10,14)长度4 -> duration=8 t=16 D登录 cur=2 peak=2 回到峰值,不创新高,不清零 t=17 E登录 cur=3 peak=3 结算[16,17)长度1 -> duration=9 (但同一刻创新高3,duration作废清零!) t=19 E登出 cur=2 peak=3 结算[17,19)长度2 -> duration=2最终结果: 峰值=3, 持续=2秒
关键就在 t=17 这一步:算法先把 [16,17) 这一小段结算进 duration,凑到 9;但紧接着发现这一刻人数冲到了 3,超过了此前的最高纪录 2——于是这个刚凑出来的 9 整个作废,duration 归零重新从这一刻算起。最终真正算数的"峰值持续时长"只有 2 秒(t=17 到 t=19),跟三个人合力攒了 9 秒的"2 人在线"记录完全不是一回事——差分数组版和暴力验证同样跑出 (3, 2),三方一致。
排序事件版按"分段起止时间相减"算时长,差分数组版按"逐秒累加"算时长——两种完全不同的实现方式,拿 30 万条真实随机记录交叉验证结果是否一致:
N = 300_000 sweep_peak_duration(records) # 排序事件版 diff_array_peak_duration(records, 86400) # 差分数组版排序事件版: peak=110353 duration=2, 耗时=856.4ms 差分数组版: peak=110353 duration=2, 耗时=15.2ms 两者完全一致: True
两种算法算出的峰值和持续时长完全一致,互相印证了"分段相减"和"逐秒累加"这两种不同的实现思路是等价的。顺带也再次验证了上一篇提到的性能差距:差分数组版快了约 56 倍,因为它不需要排序。
点击"下一步"或"播放"开始。