2026/11/2 20:45:44

滑动窗口计数:队列实现LeetCode 933题最优解

滑动窗口计数:队列实现LeetCode 933题最优解 1. 问题背景与需求分析最近在刷LeetCode时遇到了第933题最近的请求次数题目要求设计一个RecentCounter类来计算特定时间窗口内的请求次数。这类问题在实际开发中非常常见比如我们需要统计最近5分钟的访问量、监控系统需要计算过去1小时的错误次数等。题目具体要求如下实现RecentCounter类包含ping(int t)方法表示在时间t发起一个请求返回过去3000毫秒内包括当前请求发生的请求次数保证每次ping调用的时间参数t是严格递增的例如输入[RecentCounter,ping,ping,ping,ping], [[],[1],[100],[3001],[3002]] 输出[null,1,2,3,3]2. 暴力解法与性能问题2.1 直观思路的实现最直观的解法是维护所有请求的时间戳列表每次ping时遍历整个列表统计符合条件的请求数class RecentCounter { private: vectorint requests; public: RecentCounter() {} int ping(int t) { requests.push_back(t); int count 0; for(int time : requests) { if(time t - 3000 time t) { count; } } return count; } };2.2 时间复杂度分析这种解法在每次ping时需要遍历所有历史请求时间复杂度为O(N)其中N是请求总数。随着请求数量增加性能会线性下降。当请求量很大时比如百万级请求这种解法显然不可行。提示在实际工程中这种暴力解法基本不可用特别是在高并发场景下会严重影响系统性能。3. 队列优化解法3.1 队列的核心思想观察题目特点可以发现请求时间是严格递增的后到的请求时间一定大于之前的我们只需要关心最近3000ms内的请求超出时间窗口的旧请求可以丢弃基于这些特点我们可以使用队列来优化每次新请求入队从队首开始移除所有超出3000ms窗口的旧请求队列中剩余的元素数量就是我们需要的结果3.2 具体实现代码#include queue class RecentCounter { private: std::queueint q; public: RecentCounter() {} int ping(int t) { q.push(t); while(q.front() t - 3000) { q.pop(); } return q.size(); } };3.3 时间复杂度分析虽然看起来有while循环但每个元素最多入队和出队各一次所以平摊时间复杂度是O(1)。空间复杂度是O(L)其中L是3000ms窗口内最大可能的请求数。4. 边界条件与测试用例4.1 关键边界情况第一个请求输入ping(1)预期输出1请求间隔刚好等于3000ms序列ping(1), ping(3001), ping(6001)预期输出1, 2, 2密集请求短时间内大量请求如1ms间隔验证队列不会无限增长大时间跨度请求间隔远大于3000ms验证旧请求被正确移除4.2 完整测试示例int main() { RecentCounter rc; cout rc.ping(1) endl; // 1 cout rc.ping(100) endl; // 2 cout rc.ping(3001) endl; // 3 cout rc.ping(3002) endl; // 3 return 0; }5. 实际工程中的应用变种5.1 分布式环境下的实现在分布式系统中请求可能来自不同节点需要考虑时钟同步问题各节点时间可能不完全一致并发访问需要线程安全的队列实现持久化存储防止系统崩溃丢失计数5.2 滑动窗口的高级实现对于更复杂的滑动窗口统计可以考虑环形缓冲区固定大小数组实现高效滑动窗口时间分桶将时间划分为固定间隔的桶进行统计概率数据结构如HyperLogLog用于近似计数5.3 其他编程语言实现Python实现示例from collections import deque class RecentCounter: def __init__(self): self.q deque() def ping(self, t: int) - int: self.q.append(t) while self.q[0] t - 3000: self.q.popleft() return len(self.q)Java实现示例import java.util.LinkedList; import java.util.Queue; class RecentCounter { private QueueInteger queue; public RecentCounter() { queue new LinkedList(); } public int ping(int t) { queue.add(t); while(queue.peek() t - 3000) { queue.poll(); } return queue.size(); } }6. 性能优化与进阶思考6.1 内存优化对于极高频请求场景可以考虑预先分配固定大小队列使用循环队列避免动态扩容开销对于时间精度要求不高的场景可以降低时间戳精度如秒级6.2 多时间窗口统计如果需要同时统计多个时间窗口如最近1s、5s、30s可以维护多个不同长度的队列使用分层时间窗口算法考虑使用专门的时序数据库6.3 替代数据结构比较除了队列其他可能的数据结构双端队列与普通队列性能相近跳表可以快速定位时间边界时间序列数据库适合长期历史数据统计7. 常见错误与调试技巧7.1 典型错误模式忘记处理空队列情况// 错误代码 while(q.front() t - 3000) { // 当队列为空时front()会出错 q.pop(); }使用错误的时间窗口判断// 错误代码 - 错误地使用了 while(q.front() t - 3000) { q.pop(); }没有利用时间递增的特性做了不必要的全量扫描7.2 调试建议打印队列内容在每次ping后打印队列状态验证边界条件特别是第一个和最后一个请求压力测试模拟高频请求验证性能8. 从这道题学到的编程思维利用问题特性优化时间严格递增的特性让我们可以用更高效的方式维护窗口空间换时间使用额外空间队列存储必要信息换取时间效率边界思维明确时间窗口的包含关系是否包含边界值数据结构选择根据操作特点先进先出选择最合适的结构在实际工程中这类滑动窗口计数问题非常常见掌握这种队列解法后可以举一反三应用到API调用频率限制系统负载监控实时数据分析用户行为统计我在实际项目中就曾用类似的方法实现过一个实时流量监控系统核心思路与这道题完全一致。关键是要理解问题本质选择合适的数据结构而不是一上来就写暴力解法。