Taro Logo

Number of Recent Calls

Easy
Google logo
Google
1 view
Topics:
StacksArrays

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:

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 <= 10^9
  • Each test case will call ping with strictly increasing values of t.
  • At most 10^4 calls will be made to ping.

Solution


Recent Counter Problem

Problem Description

We are tasked with designing a RecentCounter class that counts the number of recent requests within a 3000 millisecond time frame. Specifically:

  • The RecentCounter() constructor initializes the counter with zero requests.
  • The ping(int t) method adds a new request at time t and returns the number of requests that occurred within the range [t - 3000, t]. It's guaranteed that t will always be larger than the previous t value.

Naive Solution

A straightforward approach is to store all incoming timestamps in a list. For each ping(t) call, we add the current timestamp t to the list and then iterate through the list, counting the timestamps that fall within the range [t - 3000, t]. Finally, we return the count.

Code (Python)

class RecentCounter:
    def __init__(self):
        self.requests = []

    def ping(self, t: int) -> int:
        self.requests.append(t)
        count = 0
        for req_time in self.requests:
            if t - 3000 <= req_time <= t:
                count += 1
        return count

Time Complexity

O(N) for each ping() call, where N is the number of requests stored so far. This is because, in the worst case, we iterate through the entire requests list.

Space Complexity

O(N), where N is the number of requests stored, since the requests list can grow linearly with the number of calls to ping().

Optimal Solution

We can optimize the solution by using a queue to store the requests. The key idea is to maintain only the relevant requests (those within the last 3000 milliseconds) in the queue. For each ping(t) call:

  1. Add the new timestamp t to the queue.
  2. Remove any timestamps from the front of the queue that are outside the range [t - 3000, t]. In other words, remove elements that are less than t - 3000.
  3. Return the current size of the queue, which represents the number of recent requests.

Code (Python)

from collections import deque

class RecentCounter:
    def __init__(self):
        self.requests = deque()

    def ping(self, t: int) -> int:
        self.requests.append(t)
        while self.requests[0] < t - 3000:
            self.requests.popleft()
        return len(self.requests)

Time Complexity

O(1) on average for each ping() call. Although there's a while loop, in practice, the number of elements removed from the queue in each ping call is typically small. Thus the amortized time complexity is closer to O(1).

Space Complexity

O(W), where W is the maximum number of requests within a 3000ms window. In the worst case, if requests come in very rapidly, the queue might store all of them within that window, but the size is bounded by the window size, not the total number of pings made.

Edge Cases

  • First ping: The first time ping() is called, the queue will be empty initially, and the result will always be 1. The algorithm handles this correctly.
  • Rapid pings: If calls to ping() occur in rapid succession (e.g., ping(1), ping(2), ping(3)...), the queue will grow quickly. The algorithm will correctly store all the requests within the 3000ms window.
  • Sparse pings: If calls to ping() are far apart, the queue will effectively be cleared each time, storing only the most recent request.