362. Design Hit Counter
On LeetCode ->Problem¶
Design HitCounter with chronologically ordered calls; equal timestamps are allowed.
hit(t)records a hit at secondt.getHits(t)counts hits whose timestamps satisfy \(t - 300 < s \le t\).- Support many hits at the same second without storing each separately.
class HitCounter:
def __init__(self):
pass
def hit(self, timestamp: int) -> None:
pass
def getHits(self, timestamp: int) -> int:
pass
Key trick¶
Keep a deque of (timestamp, count) buckets and a running total; subtract expired buckets before either operation.
Trap¶
A hit exactly 300 seconds old is expired, and "monotonically increasing" here means nondecreasing: repeated timestamps are valid.
Why is it interesting?¶
It combines sliding-window expiration with aggregation, making storage independent of the number of hits per second.
Python solution¶
from collections import deque
class HitCounter:
def __init__(self):
self.q = deque()
self.total = 0
def _expire(self, timestamp: int) -> None:
# The window excludes timestamp - 300.
while self.q and self.q[0][0] <= timestamp - 300:
_, cnt = self.q.popleft()
self.total -= cnt
def hit(self, timestamp: int) -> None:
self._expire(timestamp)
# Aggregate simultaneous hits instead of storing each hit.
if self.q and self.q[-1][0] == timestamp:
_, cnt = self.q[-1]
self.q[-1] = (timestamp, cnt + 1)
else:
self.q.append((timestamp, 1))
self.total += 1
def getHits(self, timestamp: int) -> int:
self._expire(timestamp)
return self.total
- Time: \(O(1)\) amortized per call; \(O(300)\) worst case when expiring buckets.
- Space: \(O(300)\), with at most one bucket per second in the window.
Comment on my solution¶
Your deque of aggregated timestamps is the right approach; the stub is unfinished.
- Maintain
total: increment on each hit and subtract each expired bucket's count. - No timestamp-to-index map is needed.
- A query timestamp need not exist in the deque: it only determines which buckets expire.
- Cumulative sums alone would retain unnecessary history and require searching for the window boundary.
# Ideas
# - need some sort of counter
# - maybe do cumulative sum??
# - keep number of hit per second (per timestamp)
# - I'm not sure about "monotonically increasing",
# does this allow repeated number like 1, 2, 2, 3
# - how to avoid scanning all last 300 seconds?
# [. . .. . .. ... .]
# - keep sliding window of (timestamp, hits)
# - when new hit updated last pushed tuple if same timestamp
# - deque old tuple no longer in 300 span window
# - count total hit in queue and store result for last timestamp
# - but how?
# - with a list + a map of timestamp to index in that list
# - This doesn't work because when getHits() with a non existing
# timestamp, we can count the hits, because that have been aggregated
# in other timestamp
from collections import deque
class HitCounter:
def __init__(self):
def hit(self, timestamp: int):
def getHits(self, timestamp: int):
Extra¶
Problem description¶
Design a hit counter which counts the number of hits received in the
past 5 minutes (i.e., the past 300 seconds).
Your system should accept a timestamp parameter (in seconds
granularity), and you may assume that calls are being made to the system
in chronological order (i.e., timestamp is monotonically increasing).
Several hits may arrive roughly at the same time.
Implement the HitCounter class:
HitCounter()Initializes the object of the hit counter system.void hit(int timestamp)Records a hit that happened attimestamp(in seconds). Several hits may happen at the sametimestamp.int getHits(int timestamp)Returns the number of hits in the past 5 minutes fromtimestamp(i.e., the past300seconds).
Example 1:
Input
["HitCounter", "hit", "hit", "hit", "getHits", "hit", "getHits", "getHits"]
[[], [1], [2], [3], [4], [300], [300], [301]]
Output
[null, null, null, null, 3, null, 4, 3]
Explanation
HitCounter hitCounter = new HitCounter();
hitCounter.hit(1); // hit at timestamp 1.
hitCounter.hit(2); // hit at timestamp 2.
hitCounter.hit(3); // hit at timestamp 3.
hitCounter.getHits(4); // get hits at timestamp 4, return 3.
hitCounter.hit(300); // hit at timestamp 300.
hitCounter.getHits(300); // get hits at timestamp 300, return 4.
hitCounter.getHits(301); // get hits at timestamp 301, return 3.
Constraints:
1 <= timestamp <= 2 * 10^9- All the calls are being made to the system in chronological order
(i.e.,
timestampis monotonically increasing). - At most
300calls will be made tohitandgetHits.
Follow up: What if the number of hits per second could be huge? Does your design scale?
Clarification of the problem description¶
I think there something I didn't get in the problem description. If we received
hit(500), can I ask after forgetHits(300)? This is what I understood, and in that case the sliding window alone with total is not enough because it didn't store all hit history.
Yes: the chronological-order guarantee applies to all calls, including both hit and getHits.
After hit(500), calling getHits(300) is not allowed.
# Valid: timestamps never decrease.
counter.hit(500)
counter.getHits(500)
counter.getHits(600)
counter.hit(600)
# Invalid: time moves backward.
counter.hit(500)
counter.getHits(300)
Here, "monotonically increasing" means nondecreasing: equal timestamps are allowed.
That guarantee makes the sliding window safe: once a hit expires, no future valid query can include it. Your concern would be correct if historical queries were allowed; that would require a different design that preserves history.