A clear explanation of solving Number of Recent Calls using a queue as a sliding time window.
Problem Restatement
We need to implement a class called RecentCounter.
It supports one main operation:
ping(t)Each call records a request at time t, measured in milliseconds.
After recording the request, ping(t) must return how many requests happened in the time range:
[t - 3000, t]The current request is included.
The problem guarantees that every new t is strictly larger than the previous one. The official statement defines RecentCounter(), ping(t), and the inclusive recent window [t - 3000, t].
Input and Output
| Item | Meaning |
|---|---|
| Constructor | Initializes an empty counter |
ping(t) input | A request time in milliseconds |
ping(t) output | Number of requests in [t - 3000, t] |
| Important guarantee | Each t is strictly increasing |
Class shape:
class RecentCounter:
def __init__(self):
...
def ping(self, t: int) -> int:
...Examples
Example:
Input:
["RecentCounter", "ping", "ping", "ping", "ping"]
[[], [1], [100], [3001], [3002]]
Output:
[null, 1, 2, 3, 3]Step by step:
ping(1)Valid range:
[-2999, 1]Only request 1 is inside.
Return:
1Next:
ping(100)Valid range:
[-2900, 100]Requests 1 and 100 are inside.
Return:
2Next:
ping(3001)Valid range:
[1, 3001]Requests 1, 100, and 3001 are inside.
Return:
3Next:
ping(3002)Valid range:
[2, 3002]Request 1 is now too old.
Requests 100, 3001, and 3002 are inside.
Return:
3First Thought: Store All Requests and Count Each Time
One simple solution is to store every request time in a list.
For each ping(t), scan the whole list and count how many values are at least t - 3000.
That works, but each call may scan many old requests that can never be useful again.
Because t always increases, once a request is older than t - 3000, it will also be too old for all future calls.
So we should remove old requests instead of keeping them forever.
Key Insight
This is a sliding window over time.
For each new time t, the valid window is:
[t - 3000, t]Since calls arrive in increasing order, request times are naturally sorted.
The oldest requests are always at the front.
So we can use a queue:
- Push the new request time to the back.
- Remove request times from the front while they are too old.
- The queue length is the answer.
A request is too old when:
queue[0] < t - 3000The range is inclusive, so a request at exactly t - 3000 must stay.
Algorithm
Initialize an empty queue:
self.q = deque()For each ping(t):
- Append
t. - Compute the oldest valid time:
left = t - 3000- While the queue front is smaller than
left, remove it. - Return the queue length.
Walkthrough
For calls:
ping(1), ping(100), ping(3001), ping(3002)| Call | Queue after append | Remove old values | Queue after cleanup | Return |
|---|---|---|---|---|
ping(1) | [1] | none | [1] | 1 |
ping(100) | [1, 100] | none | [1, 100] | 2 |
ping(3001) | [1, 100, 3001] | none, because 1 is valid | [1, 100, 3001] | 3 |
ping(3002) | [1, 100, 3001, 3002] | remove 1 | [100, 3001, 3002] | 3 |
The answer sequence is:
[1, 2, 3, 3]Correctness
The queue stores request times that have been recorded and have not yet become too old.
When ping(t) is called, the algorithm first appends t, so the current request is included.
Then it removes every request time smaller than t - 3000. These requests are outside the required range [t - 3000, t], so they must not be counted.
It keeps every request time greater than or equal to t - 3000. Since all request times are at most t at that moment, every remaining request lies inside [t - 3000, t].
Because t values are strictly increasing, the queue is sorted by time. Therefore, all too-old requests appear at the front, and removing from the front removes exactly the expired requests.
After cleanup, the queue contains exactly the recent requests in the required time window. Returning its length gives the correct count.
Complexity
Let m be the number of calls to ping.
| Metric | Value | Why |
|---|---|---|
| Time per call | Amortized O(1) | Each request is appended once and removed once |
| Space | O(m) worst case | The queue stores requests still inside the active window |
Implementation
from collections import deque
class RecentCounter:
def __init__(self):
self.q = deque()
def ping(self, t: int) -> int:
self.q.append(t)
left = t - 3000
while self.q[0] < left:
self.q.popleft()
return len(self.q)Code Explanation
We keep a queue of active request times:
self.q = deque()Each new request is appended:
self.q.append(t)The oldest valid timestamp is:
left = t - 3000Then we remove expired requests:
while self.q[0] < left:
self.q.popleft()We use < left, not <= left, because the range includes t - 3000.
Finally, the number of recent requests is exactly the queue length:
return len(self.q)Testing
def run_tests():
rc = RecentCounter()
assert rc.ping(1) == 1
assert rc.ping(100) == 2
assert rc.ping(3001) == 3
assert rc.ping(3002) == 3
rc = RecentCounter()
assert rc.ping(3000) == 1
assert rc.ping(6000) == 2
assert rc.ping(6001) == 2
rc = RecentCounter()
assert rc.ping(1) == 1
assert rc.ping(3002) == 1
assert rc.ping(6003) == 1
print("all tests passed")
run_tests()| Test | Why |
|---|---|
1, 100, 3001, 3002 | Official example |
3000, 6000 | Boundary value exactly t - 3000 stays |
6001 after 3000 | Value older than window is removed |
| Large gaps | Queue can shrink to one request |