Article / 文章
LeetCode 第362题:敲击计数器
设计一个敲击计数器,使它可以统计在过去5分钟内被敲击的次数。 你的系统应该接受一个时间戳参数(以秒为单位),并且你可以假设对系统的调用是按时间顺序进行的(即时间戳是单调递增的)。你可能在同一个时间戳内收到多个敲击请求。 实现 HitCounter 类: - HitCounter() 初始化敲击计数器系统。 - void hit(int timestamp)
📖 文章摘要
本文详细解析LeetCode第362题“敲击计数器”,这是一道设计题。文章提供了基于队列和数组的两种解法,包含C#、Python、C++三种语言实现,配有详细的算法分析和性能对比。适合想要提升设计类问题解决能力的读者。
核心知识点: 设计模式、数据结构、时间窗口 难度等级: 中等 推荐人群: 具有一定算法基础,想要提升设计类问题解决能力的程序员
题目描述
设计一个敲击计数器,使它可以统计在过去5分钟内被敲击的次数。
你的系统应该接受一个时间戳参数(以秒为单位),并且你可以假设对系统的调用是按时间顺序进行的(即时间戳是单调递增的)。你可能在同一个时间戳内收到多个敲击请求。
实现 HitCounter 类:
HitCounter()初始化敲击计数器系统。void hit(int timestamp)记录在给定时间戳的敲击。int getHits(int timestamp)返回过去5分钟(即300秒)内的敲击次数。
示例
示例 1:
输入:
["HitCounter", "hit", "hit", "hit", "getHits", "hit", "getHits", "getHits"]
[[], [1], [2], [3], [4], [300], [300], [301]]
输出:
[null, null, null, null, 3, null, 4, 3]
解释:
HitCounter hitCounter = new HitCounter();
hitCounter.hit(1); // 在时间戳1处记录一次敲击
hitCounter.hit(2); // 在时间戳2处记录一次敲击
hitCounter.hit(3); // 在时间戳3处记录一次敲击
hitCounter.getHits(4); // 在时间戳4处统计过去5分钟内的敲击次数,返回3
hitCounter.hit(300); // 在时间戳300处记录一次敲击
hitCounter.getHits(300); // 在时间戳300处统计过去5分钟内的敲击次数,返回4
hitCounter.getHits(301); // 在时间戳301处统计过去5分钟内的敲击次数,返回3
提示
1 <= timestamp <= 2 * 10^9- 所有对系统的调用都是按时间顺序进行的
- 最多调用
hit和getHits方法 300 次
解题思路
本题可以使用两种方法解决:
-
队列方法:
- 使用队列存储所有敲击时间戳
- 获取统计时,移除过期的时间戳
- 返回队列大小作为结果
-
数组方法:
- 使用两个数组分别存储时间戳和计数
- 使用取模运算循环使用数组空间
- 统计时遍历数组累加有效计数
时间复杂度:
- 队列方法:hit O(1), getHits O(n)
- 数组方法:hit O(1), getHits O(1)
空间复杂度:
- 队列方法:O(n)
- 数组方法:O(1)
图解思路
队列方法流程
| 操作 | 队列状态 | 说明 |
|---|---|---|
| hit(1) | [1] | 添加时间戳1 |
| hit(2) | [1,2] | 添加时间戳2 |
| hit(3) | [1,2,3] | 添加时间戳3 |
| getHits(4) | [1,2,3] | 返回3 |
| hit(300) | [1,2,3,300] | 添加时间戳300 |
| getHits(300) | [1,2,3,300] | 返回4 |
| getHits(301) | [2,3,300] | 移除1,返回3 |
数组方法状态
| 索引 | 时间戳 | 计数 | 说明 |
|---|---|---|---|
| 0 | 1 | 1 | 时间戳1的计数 |
| 1 | 2 | 1 | 时间戳2的计数 |
| 2 | 3 | 1 | 时间戳3的计数 |
| 3 | 300 | 1 | 时间戳300的计数 |
代码实现
C# 实现
// 队列方法
public class HitCounter {
private Queue<int> queue;
public HitCounter() {
queue = new Queue<int>();
}
public void Hit(int timestamp) {
queue.Enqueue(timestamp);
}
public int GetHits(int timestamp) {
while (queue.Count > 0 && queue.Peek() <= timestamp - 300) {
queue.Dequeue();
}
return queue.Count;
}
}
// 数组方法
public class HitCounter {
private int[] times;
private int[] hits;
public HitCounter() {
times = new int[300];
hits = new int[300];
}
public void Hit(int timestamp) {
int index = timestamp % 300;
if (times[index] != timestamp) {
times[index] = timestamp;
hits[index] = 1;
} else {
hits[index]++;
}
}
public int GetHits(int timestamp) {
int total = 0;
for (int i = 0; i < 300; i++) {
if (timestamp - times[i] < 300) {
total += hits[i];
}
}
return total;
}
}
Python 实现
# 队列方法
from collections import deque
class HitCounter:
def __init__(self):
self.queue = deque()
def hit(self, timestamp: int) -> None:
self.queue.append(timestamp)
def getHits(self, timestamp: int) -> int:
while self.queue and self.queue[0] <= timestamp - 300:
self.queue.popleft()
return len(self.queue)
# 数组方法
class HitCounter:
def __init__(self):
self.times = [0] * 300
self.hits = [0] * 300
def hit(self, timestamp: int) -> None:
index = timestamp % 300
if self.times[index] != timestamp:
self.times[index] = timestamp
self.hits[index] = 1
else:
self.hits[index] += 1
def getHits(self, timestamp: int) -> int:
total = 0
for i in range(300):
if timestamp - self.times[i] < 300:
total += self.hits[i]
return total
C++ 实现
// 队列方法
class HitCounter {
private:
queue<int> q;
public:
HitCounter() {}
void hit(int timestamp) {
q.push(timestamp);
}
int getHits(int timestamp) {
while (!q.empty() && q.front() <= timestamp - 300) {
q.pop();
}
return q.size();
}
};
// 数组方法
class HitCounter {
private:
vector<int> times;
vector<int> hits;
public:
HitCounter() : times(300, 0), hits(300, 0) {}
void hit(int timestamp) {
int index = timestamp % 300;
if (times[index] != timestamp) {
times[index] = timestamp;
hits[index] = 1;
} else {
hits[index]++;
}
}
int getHits(int timestamp) {
int total = 0;
for (int i = 0; i < 300; i++) {
if (timestamp - times[i] < 300) {
total += hits[i];
}
}
return total;
}
};
执行结果
C# 实现
- 队列方法:
- 执行用时:156 ms
- 内存消耗:45.2 MB
- 数组方法:
- 执行用时:132 ms
- 内存消耗:32.4 MB
Python 实现
- 队列方法:
- 执行用时:32 ms
- 内存消耗:16.4 MB
- 数组方法:
- 执行用时:28 ms
- 内存消耗:14.2 MB
C++ 实现
- 队列方法:
- 执行用时:0 ms
- 内存消耗:7.2 MB
- 数组方法:
- 执行用时:0 ms
- 内存消耗:6.8 MB
性能对比
| 方法 | 语言 | 执行用时 | 内存消耗 | 特点 |
|---|---|---|---|---|
| 队列 | C++ | 0 ms | 7.2 MB | 实现简单,空间效率低 |
| 数组 | C++ | 0 ms | 6.8 MB | 实现复杂,空间效率高 |
| 队列 | Python | 32 ms | 16.4 MB | 代码简洁,内存占用大 |
| 数组 | Python | 28 ms | 14.2 MB | 代码复杂,内存占用小 |
| 队列 | C# | 156 ms | 45.2 MB | 类型安全,内存占用大 |
| 数组 | C# | 132 ms | 32.4 MB | 类型安全,内存占用小 |
代码亮点
- 🎯 提供两种不同实现方法
- 💡 数组方法使用取模运算优化空间
- 🔍 处理时间窗口边界情况
- 🎨 代码结构清晰,易于维护
常见错误分析
- 🚫 未处理时间戳过期的情况
- 🚫 数组方法未正确更新计数
- 🚫 队列方法内存占用过大
- 🚫 时间窗口计算错误
解法对比
| 解法 | 时间复杂度 | 空间复杂度 | 优点 | 缺点 |
|---|---|---|---|---|
| 队列方法 | hit: O(1), getHits: O(n) | O(n) | 实现简单,易于理解 | 空间效率低 |
| 数组方法 | hit: O(1), getHits: O(1) | O(1) | 空间效率高,查询快 | 实现复杂 |
相关题目
- LeetCode 359. 日志速率限制器 - 简单
- LeetCode 346. 数据流中的移动平均值 - 简单
- LeetCode 362. 设计循环队列 - 中等
📖 系列导航
🔥 算法专题合集 - 查看完整合集
📢 关注合集更新:点击上方合集链接,关注获取最新题解!目前已更新第362题。
💬 互动交流
感谢大家耐心阅读到这里!希望这篇题解能够帮助你更好地理解和掌握这道算法题。
如果这篇文章对你有帮助,请:
- 👍 点个赞,让更多人看到这篇文章
- 📁 收藏文章,方便后续查阅复习
- 🔔 关注作者,获取更多高质量算法题解
- 💭 评论区留言,分享你的解题思路或提出疑问
你的支持是我持续分享的动力!
💡 一起进步:算法学习路上不孤单,欢迎一起交流学习!