The "Number of Recent Calls" problem requires you to design a class called RecentCounter
that counts the number of calls received in the last 3000 milliseconds. Specifically, you need to implement two things:
ping(int t)
that records a new call at time t
(measured in milliseconds), and returns the number of calls that have happened in the inclusive range [t - 3000, t]
.
The function will be called with strictly increasing values of t
. You must efficiently keep track of calls in the last 3000 milliseconds and return the count for each ping
call.
Constraints:
t
is strictly increasing.1 <= t <= 10^9
10^4
calls to ping
will be made.
At first glance, it seems we need to keep track of all the call times and count how many fall within the last 3000 milliseconds for each ping
. A brute-force approach would be to store all call times and, for each new call, scan through the list and count those in the valid range. However, this would be inefficient as the number of calls increases.
Since t
is always increasing, we can take advantage of this property. We only need to keep track of call times within the window [t - 3000, t]
. Older calls (before t - 3000
) can be discarded since they will never be counted in future ping
calls.
This leads us to consider using a queue data structure, which allows for efficient removal of old calls from the front and addition of new calls to the back.
We will use a queue to store the timestamps of the calls. Each time ping(t)
is called:
t
to the queue.t - 3000
.
Why a queue? Because it allows us to efficiently remove the oldest elements (calls) that are no longer within the valid window, and append new calls at the end. Since t
is always increasing, the oldest call is always at the front of the queue.
Step-by-step:
ping(t)
:
t
to the queue.t - 3000
, remove it.
Let's look at a sample sequence:
RecentCounter rc = new RecentCounter();
rc.ping(1); // returns 1
rc.ping(100); // returns 2
rc.ping(3001); // returns 3
rc.ping(3002); // returns 3
The queue always contains only those calls in the last 3000 ms, and its length is the answer.
Brute-force approach: For each ping
, scan all previous calls and count those in the valid window.
Optimized queue approach:
This is efficient and suitable for the problem's constraints.
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)
#include <queue>
class RecentCounter {
public:
std::queue<int> q;
RecentCounter() {
}
int ping(int t) {
q.push(t);
while (!q.empty() && q.front() < t - 3000) {
q.pop();
}
return q.size();
}
};
import java.util.*;
class RecentCounter {
Queue<Integer> q;
public RecentCounter() {
q = new LinkedList<>();
}
public int ping(int t) {
q.offer(t);
while (q.peek() < t - 3000) {
q.poll();
}
return q.size();
}
}
class RecentCounter {
constructor() {
this.q = [];
}
ping(t) {
this.q.push(t);
while (this.q[0] < t - 3000) {
this.q.shift();
}
return this.q.length;
}
}
The "Number of Recent Calls" problem is elegantly solved by taking advantage of the strictly increasing nature of the timestamps. By using a queue to store only the relevant call times, we can efficiently add new calls and remove outdated ones, ensuring that each ping
operation runs in amortized constant time. The key insight is to maintain a sliding window of recent calls, which is a common pattern in time-based counting problems. This approach is both simple and optimal for the given constraints.