Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

933. Number of Recent Calls - Leetcode Solution

Problem Description

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:

  • A constructor that initializes the counter.
  • A method 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:

  • Each t is strictly increasing.
  • 1 <= t <= 10^9
  • At most 10^4 calls to ping will be made.

Thought Process

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.

Solution Approach

We will use a queue to store the timestamps of the calls. Each time ping(t) is called:

  1. We add t to the queue.
  2. We remove timestamps from the front of the queue as long as they are less than t - 3000.
  3. After this cleanup, the size of the queue gives us the number of calls in the last 3000 milliseconds.

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:

  • Initialize an empty queue in the constructor.
  • For each ping(t):
    • Add t to the queue.
    • While the front of the queue is less than t - 3000, remove it.
    • Return the length of the queue.

Example Walkthrough

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

  • After rc.ping(1): Queue: [1]. Only one call in the last 3000 ms.
  • After rc.ping(100): Queue: [1, 100]. Both 1 and 100 are within [100-3000, 100] = [-2900, 100]. So, 2 calls.
  • After rc.ping(3001): Queue: [1, 100, 3001]. All three are within [1, 3001]. So, 3 calls.
  • After rc.ping(3002):
    • Add 3002 to the queue: [1, 100, 3001, 3002].
    • Now remove from the front: 1 is less than 3002-3000 = 2, so remove 1.
    • Queue is now [100, 3001, 3002]. 100 is within [2, 3002], so we keep it.
    • Return queue length: 3.

The queue always contains only those calls in the last 3000 ms, and its length is the answer.

Time and Space Complexity

Brute-force approach: For each ping, scan all previous calls and count those in the valid window.

  • Time Complexity: O(N) per ping, where N is the number of pings so far.
  • Space Complexity: O(N) to store all call times.

Optimized queue approach:

  • Time Complexity: Each call is added and removed from the queue at most once, so over all pings, total work is O(N). Thus, amortized O(1) per ping.
  • Space Complexity: O(W), where W is the maximum number of calls in any 3000 ms window (at most N, but usually much less).

This is efficient and suitable for the problem's constraints.

Code Implementation

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;
    }
}
      

Summary

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.