933. Number of Recent Calls
Problem Description
In this problem, you are tasked with designing a RecentCounter
class that tracks the number of requests received within a specific time frame. The time frame in question is the last 3000 milliseconds, which equates to the last 3 seconds. This can be likened to monitoring network traffic, where you're interested in understanding the number of events or requests that have occurred in a sliding window of time for performance analysis or rate limiting.
The class should support two operations:
-
RecentCounter()
is the constructor that initializes the counter. When aRecentCounter
object is created, it starts with zero recent requests. -
ping(int t)
is a method that is called each time a new request is received. The request comes with a timestampt
(in milliseconds), which is strictly increasing with each call. The purpose ofping
is to add the new request and then return the count of all recent requests within the last 3000 milliseconds, which is the time range fromt - 3000
tot
, inclusive of both ends of the interval.
Intuition
The key to solving this problem is to maintain a dynamic list of timestamped requests, ensuring that only those within the last 3000 milliseconds are counted. Because the input guarantees that each t
in ping
is increasing, we can use a queue to keep track of the timestamps of the recent requests.
The intuition for using a queue comes from its FIFO (First-In-First-Out) property, which naturally aligns with the progression of time in our problem. When a new request arrives with timestamp t
, we add it to the end of the queue. Then we need to remove all requests from the front of the queue that are outside the 3000 millisecond window (i.e., older than t - 3000
).
By doing so, our queue always contains only those requests that are within the 3000 millisecond window. The length of the queue after this cleaning process gives us the number of recent requests, which is what the ping
method returns.
To implement this idea in code, we utilize the deque
data structure from Python's collections
module because it allows for efficient addition and removal of elements from both ends.
Learn more about Queue and Data Stream patterns.
Solution Approach
The solution is implemented as a class, RecentCounter
, containing 2 methods: the constructor __init__
and the method ping
.
-
In the
__init__
method, we initialize a queue (specifically a deque) to store the timestamps of incoming requests. We start with an empty deque since there are no requests yet. A deque is used here because it supports fast appends and pops from both ends. -
The
ping
method is where the main logic is executed:-
First, we append the new request's timestamp
t
to the end of the deque withself.q.append(t)
. This means we are adding the current request to our list of recent requests. -
Next, we enter a
while
loop, which will continue to execute as long as the front element of the deque (the oldest request) is less thant - 3000
. This check is done withself.q[0] < t - 3000
. Requests that fall outside the last 3000 milliseconds do not belong to the 'recent' category anymore and should be removed. -
For each loop iteration, if the condition is met (i.e., the request is too old), we remove the oldest request from the front of the deque with
self.q.popleft()
. Since requests are always timestamped in increasing order, once we find a request that is within the time frame, we can be sure that all subsequent requests are also within it, and there is no need to check the rest of the deque. -
Finally, once we have cleaned up all the old requests, the remaining length of the deque represents the count of recent requests. We return this count with
return len(self.q)
.
-
This implementation ensures that we're always working with a list of requests that have occurred within the last 3000 milliseconds. The time complexity for the ping
method is O(N), where N is the number of calls to this method. However, because we are only keeping recent requests within the deque, this method is effective and efficient for a large number of requests, as long as the number of concurrent recent requests is reasonable.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's use the solution approach outlined above on a small example to illustrate how the RecentCounter
class works in practice. For this example, assume we receive a sequence of timestamped requests at the following times (in milliseconds): 1, 100, 3001, 3002.
- We create a
RecentCounter
object. - We call
ping(1)
. The deque now has one element: [1].- Since there are no pings older than 3000 milliseconds, we return
len(self.q)
which is 1.
- Since there are no pings older than 3000 milliseconds, we return
- Next, we call
ping(100)
. The deque becomes: [1, 100].- Both pings are within 3000 milliseconds from the current ping time, so the number of recent requests is 2.
- Then we call
ping(3001)
. The deque is updated to: [1, 100, 3001].- Now, we check for timestamps older than
3001 - 3000
which is 1. Since 1 is within the range, we do not remove it. The deque remains [1, 100, 3001]. - The
len(self.q)
now returns 3, as these are the recent pings within the last 3000 milliseconds.
- Now, we check for timestamps older than
- Lastly, we call
ping(3002)
. The deque first receives the timestamp 3002: [1, 100, 3001, 3002].- We then remove any timestamps older than
3002 - 3000
, which is 2. After removal, the deque is [100, 3001, 3002]. - We return
len(self.q)
, which is 3, as the count of recent requests.
- We then remove any timestamps older than
Throughout this process, the RecentCounter
maintains a list of timestamps that are within the window of the last 3000 milliseconds. By adding new timestamps to the deque and removing the old ones, we ensure that we always have an accurate count of the recent requests when ping
is called.
Solution Implementation
1from collections import deque
2
3class RecentCounter:
4 def __init__(self):
5 # Initialize a double-ended queue to keep track of ping times
6 self.queue = deque()
7
8 def ping(self, t: int) -> int:
9 # Add the new ping time to the queue
10 self.queue.append(t)
11
12 # Remove ping times that are older than 3000ms from the current time
13 while self.queue[0] < t - 3000:
14 self.queue.popleft()
15
16 # Return the number of pings within the last 3000ms
17 return len(self.queue)
18
19# Example usage:
20# recent_counter = RecentCounter()
21# count = recent_counter.ping(1) # Assuming 't' is a timestamp in milliseconds
22
1import java.util.Deque;
2import java.util.ArrayDeque;
3
4class RecentCounter {
5 // Queue to store the timestamps of the pings.
6 private Deque<Integer> queue = new ArrayDeque<>();
7
8 /**
9 * Constructor initializes the RecentCounter with an empty queue.
10 */
11 public RecentCounter() {
12 }
13
14 /**
15 * Records a new ping, and returns the number of pings in the last 3000 milliseconds.
16 *
17 * @param t The timestamp of the ping.
18 * @return The count of pings in the last 3000 milliseconds.
19 */
20 public int ping(int t) {
21 // Note: Timestamp here is assumed to be in milliseconds.
22
23 // Offer/add the new ping's timestamp to the end of the queue.
24 queue.offer(t);
25
26 // Continue removing(polling) pings from the start of the queue if they are older
27 // than 3000 milliseconds compared to the current ping's timestamp.
28 while (!queue.isEmpty() && queue.peekFirst() < t - 3000) {
29 queue.pollFirst();
30 }
31
32 // After removing old pings, return the size of the queue,
33 // which is the count of recent pings.
34 return queue.size();
35 }
36}
37
38/**
39 * The RecentCounter object will be instantiated and called as such:
40 * RecentCounter obj = new RecentCounter();
41 * int param_1 = obj.ping(t);
42 */
43
1#include <deque>
2
3class RecentCounter {
4private:
5 std::deque<int> pings;
6
7public:
8 // Constructor of RecentCounter initializes a new instance.
9 RecentCounter() {
10 // Nothing needed here since the deque 'pings' automatically initializes
11 }
12
13 // Method to record a new ping and return the count of pings in the last 3000 milliseconds.
14 int ping(int t) {
15 // Add the new ping timestamp to the deque
16 pings.push_back(t);
17
18 // Remove any pings that are no longer within the past 3000 milliseconds
19 while (!pings.empty() && pings.front() < t - 3000) {
20 pings.pop_front();
21 }
22
23 // Return the count of pings within the past 3000 milliseconds
24 return pings.size();
25 }
26};
27
28/*
29 * Use of the RecentCounter class:
30 *
31 * RecentCounter* obj = new RecentCounter();
32 * int param_1 = obj->ping(t);
33 * delete obj; // Once done, remember to delete the object to free memory
34 */
35
1// Array to hold timestamps of recent pings.
2let recentPings: number[] = [];
3
4/**
5 * Records a ping with the current timestamp and returns the number
6 * of pings that occurred in the last 3000 milliseconds.
7 * @param {number} t - The current timestamp in milliseconds
8 * @returns {number} The number of pings in the last 3000 milliseconds
9 */
10function ping(t: number): number {
11 // Record the new ping by adding the timestamp to the array.
12 recentPings.push(t);
13
14 // Remove pings from the array that are older than 3000 milliseconds from the current timestamp.
15 while (recentPings.length > 0 && recentPings[0] < t - 3000) {
16 recentPings.shift();
17 }
18
19 // Return the number of recent pings.
20 return recentPings.length;
21}
22
Time and Space Complexity
Time Complexity
The time complexity of the ping
method is O(N), where N is the number of calls to the method that fall within the 3000 ms window relative to the current call. This is because in the worst-case scenario, all N calls might have occurred within the past 3000 ms, which means the while
loop will have to run N times to remove all outdated requests (requests older than 3000 ms before the current timestamp t
). However, it is important to note that each individual call to ping is generally O(1) on average because the while loop doesn't always iterate over all elements. Over time, each element is added once and removed once, leading to an amortized time complexity of O(1).
Space Complexity
The space complexity is O(N), where N is the number of all pings that are within the 3000 ms time window relative to the current call. This is due to the data structure (a deque) used to store the timestamps of pings. The space used by the deque will grow in direct proportion to the number of elements within the time window.
Learn more about how to find time and space complexity quickly using problem constraints.
Which of the following uses divide and conquer strategy?
Recommended Readings
Queue Intro Think of the last time you stood in line to buy movie tickets The first person in line gets their ticket first and any newcomers join the end of the line This system where the first person to arrive is the first to be served is a queue in real
Median of Data Stream Given a stream of numbers find the median number at any given time accurate to 1 decimal place Example add_number 1 add_number 2 add_number 3 get_median 2 0 add_number 4 get_median 2 5 Try it yourself Explanation Intuition Brute force way is to sort the entire list every time we get a new number This would be O N log N for each add_number operation One step up is to notice that the list is sorted before we add any new number to it So every
LeetCode Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way we
Want a Structured Path to Master System Design Too? Don’t Miss This!