{x}
blog image

Number of Recent Calls

You have a RecentCounter class which counts the number of recent requests within a certain time frame.

Implement the RecentCounter class:

  • RecentCounter() Initializes the counter with zero recent requests.
  • int ping(int t) Adds a new request at time t, where t represents some time in milliseconds, and returns the number of requests that has happened in the past 3000 milliseconds (including the new request). Specifically, return the number of requests that have happened in the inclusive range [t - 3000, t].

It is guaranteed that every call to ping uses a strictly larger value of t than the previous call.

 

Example 1:

Input
["RecentCounter", "ping", "ping", "ping", "ping"]
[[], [1], [100], [3001], [3002]]
Output
[null, 1, 2, 3, 3]

Explanation
RecentCounter recentCounter = new RecentCounter();
recentCounter.ping(1);     // requests = [1], range is [-2999,1], return 1
recentCounter.ping(100);   // requests = [1, 100], range is [-2900,100], return 2
recentCounter.ping(3001);  // requests = [1, 100, 3001], range is [1,3001], return 3
recentCounter.ping(3002);  // requests = [1, 100, 3001, 3002], range is [2,3002], return 3

 

Constraints:

  • 1 <= t <= 109
  • Each test case will call ping with strictly increasing values of t.
  • At most 104 calls will be made to ping.

Solution Explanation and Code:

This problem requires designing a RecentCounter class to count the number of recent requests within a 3000-millisecond window. Two approaches are presented: using a queue and using binary search.

Approach 1: Using a Queue (Optimal Solution)

This approach leverages a queue (deque in Python, Queue in Java, etc.) to efficiently track requests.

Algorithm:

  1. Initialization: The __init__ (or constructor) initializes an empty queue.
  2. ping(t):
    • Add the new request time t to the queue.
    • While the oldest request in the queue is older than t - 3000, remove it from the queue. This maintains the 3000ms window.
    • Return the size of the queue, which represents the number of requests within the window.

Time Complexity: O(N) in the worst case for ping(), where N is the number of calls to ping(). However, on average it's O(1) as most of the time the queue will only have a few elements. The while loop removes at most one element per call.

Space Complexity: O(N) in the worst case, as the queue could store up to all the requests if they are all within the 3000ms window.

Code (Python):

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)

Other languages like Java, C++, Go, JavaScript, TypeScript, and C# use similar queue-based implementations with minor syntactic differences.

This approach utilizes a sorted list (vector/array) to store request times and binary search to find the number of requests within the time window.

Algorithm:

  1. Initialization: A sorted list s is initialized to store request times.
  2. ping(t):
    • Append the new request time t to the list s.
    • Use lower_bound (or bisect_left in Python) to find the index of the first element in s that is greater than or equal to t - 3000. This efficiently identifies the requests outside the window.
    • The difference between the current size of s and the index found represents the number of requests within the window.

Time Complexity: O(log N) for ping() on average due to the binary search, where N is the number of calls to ping. However, the insertion into the sorted array is O(N) in the worst case, thus the overall time complexity becomes O(N).

Space Complexity: O(N), as the list s can potentially store all request times.

Code (C++):

#include <vector>
#include <algorithm>
 
class RecentCounter {
public:
    vector<int> s;
 
    RecentCounter() {
    }
 
    int ping(int t) {
        s.push_back(t);
        return s.size() - (lower_bound(s.begin(), s.end(), t - 3000) - s.begin());
    }
};

Python and Go versions are also provided, adapting the binary search approach to their respective standard libraries. The other languages can also use binary search or similar algorithms to achieve a similar effect, though the queue approach is more efficient in practice for this problem.

Conclusion:

The queue-based approach (Approach 1) is generally preferred due to its better average-case time complexity (O(1) vs O(log N)) and simpler implementation. The binary search approach is presented as an alternative, showcasing a different algorithmic technique. Choose the approach based on your performance needs and coding style preferences. For this problem, the queue is generally the more efficient solution.