2747. Count Zero Request Servers
Problem Description
You have n
servers numbered from 0 to n-1, and you're given a log of requests that these servers received. Each log entry is represented as [server_id, time]
, indicating that the server with id server_id
received a request at time time
.
You're also given:
- An integer
x
representing a time window size - An array
queries
containing query times
For each query time in queries[i]
, you need to determine how many servers did not receive any requests during the time interval [queries[i] - x, queries[i]]
(inclusive on both ends).
Example breakdown:
- If you have 3 servers (n=3) and x=5
- For a query at time 10, you check the interval [5, 10]
- If only server 0 and server 2 received requests in this interval, then server 1 didn't receive any requests
- So the answer for this query would be 1
The function should return an array where the i-th element represents the number of inactive servers for the i-th query in the original queries
array.
Key points:
- A server is counted as "inactive" for a query if it received zero requests during that query's time window
- The time intervals are inclusive (both start and end times are included)
- The order of results must match the original order of queries
Intuition
The key insight is that we need to efficiently count distinct servers that received requests within different time windows. If we process queries in their original order, we'd need to repeatedly scan through logs for each query, which would be inefficient.
Instead, we can process queries in chronological order (sorted by time). This allows us to use a sliding window approach where we maintain the set of active servers as we move through time.
Think of it like watching a timeline unfold:
- As time progresses, new requests come into our window (right boundary moves forward)
- Old requests fall out of our window (left boundary moves forward)
- We only need to track which servers are currently active in our window
For each query with time r
, we need to check the interval [r - x, r]
. By sorting queries by their time value, we can:
- Maintain two pointers - one for adding new logs that enter the window (
k
), and one for removing old logs that exit the window (j
) - Use a counter/hash map to track how many requests each server has in the current window
- When a server's count drops to 0, we know it's no longer active in this window
The beauty of this approach is that each log entry is processed at most twice - once when entering the window and once when leaving. This gives us an efficient solution compared to repeatedly scanning all logs for each query.
Since we process queries out of their original order, we need to keep track of each query's original index to place the results correctly in the final answer array. This is why we use sorted(zip(queries, count()))
to pair each query with its original position.
Learn more about Sorting and Sliding Window patterns.
Solution Approach
The solution implements an offline query processing technique with sliding window and two pointers. Here's how it works step by step:
1. Initial Setup:
- Sort the logs by time:
logs.sort(key=lambda x: x[1])
- Create a Counter to track active servers in the current window:
cnt = Counter()
- Initialize two pointers
j = 0
(left boundary) andk = 0
(right boundary) - Create result array:
ans = [0] * len(queries)
2. Process Queries in Sorted Order:
- Sort queries by their time value while preserving original indices:
sorted(zip(queries, count()))
- This pairs each query time with its original position:
(query_time, original_index)
3. For Each Query (r, i)
:
- Calculate window boundaries: left boundary
l = r - x
, right boundary isr
4. Expand Window (Move Right Pointer):
while k < len(logs) and logs[k][1] <= r:
cnt[logs[k][0]] += 1
k += 1
- Add all logs with time ≤
r
to the window - Increment the count for each server that received a request
5. Shrink Window (Move Left Pointer):
while j < len(logs) and logs[j][1] < l:
cnt[logs[j][0]] -= 1
if cnt[logs[j][0]] == 0:
cnt.pop(logs[j][0])
j += 1
- Remove all logs with time <
l
from the window - Decrement the count for each server
- If a server's count reaches 0, remove it from the counter entirely
6. Calculate Result:
ans[i] = n - len(cnt)
- The number of inactive servers = total servers - number of servers in the counter
- Store result at the original index
i
Key Data Structures:
- Counter/HashMap (
cnt
): Tracks how many requests each server has in the current window - Two Pointers (
j
,k
): Maintain sliding window boundaries efficiently - Sorted Arrays: Both logs and queries are processed in time-sorted order
Time Complexity: O(m log m + q log q)
where m
is the number of logs and q
is the number of queries
Space Complexity: O(n)
for the counter in worst case when all servers are active
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example to illustrate the solution approach.
Given:
n = 3
servers (numbered 0, 1, 2)logs = [[1, 3], [2, 6], [1, 5], [0, 8], [1, 9]]
x = 5
(time window size)queries = [10, 6, 4]
Step 1: Sort the logs by time
logs = [[1, 3], [1, 5], [2, 6], [0, 8], [1, 9]]
Step 2: Pair queries with their original indices and sort by time
Original: queries = [10, 6, 4] with indices [0, 1, 2] Sorted: [(4, 2), (6, 1), (10, 0)]
Step 3: Process each sorted query
Query (4, 2): Time = 4, Original index = 2
- Window: [4-5, 4] = [-1, 4]
- Move right pointer
k
to include logs with time ≤ 4:- Add log [1, 3]:
cnt = {1: 1}
- k stops at log [1, 5] (time 5 > 4)
- Add log [1, 3]:
- Move left pointer
j
to exclude logs with time < -1:- No logs to remove (all times are positive)
- Active servers: {1}
- Inactive servers: 3 - 1 = 2 (servers 0 and 2 are inactive)
ans[2] = 2
Query (6, 1): Time = 6, Original index = 1
- Window: [6-5, 6] = [1, 6]
- Move right pointer
k
to include logs with time ≤ 6:- Add log [1, 5]:
cnt = {1: 2}
- Add log [2, 6]:
cnt = {1: 2, 2: 1}
- k stops at log [0, 8] (time 8 > 6)
- Add log [1, 5]:
- Move left pointer
j
to exclude logs with time < 1:- No logs to remove (log [1, 3] has time 3 ≥ 1)
- Active servers: {1, 2}
- Inactive servers: 3 - 2 = 1 (server 0 is inactive)
ans[1] = 1
Query (10, 0): Time = 10, Original index = 0
- Window: [10-5, 10] = [5, 10]
- Move right pointer
k
to include logs with time ≤ 10:- Add log [0, 8]:
cnt = {1: 2, 2: 1, 0: 1}
- Add log [1, 9]:
cnt = {1: 3, 2: 1, 0: 1}
- k reaches end of logs
- Add log [0, 8]:
- Move left pointer
j
to exclude logs with time < 5:- Remove log [1, 3]:
cnt = {1: 2, 2: 1, 0: 1}
- j stops at log [1, 5] (time 5 ≥ 5)
- Remove log [1, 3]:
- Active servers: {0, 1, 2}
- Inactive servers: 3 - 3 = 0 (all servers are active)
ans[0] = 0
Final Result:
ans = [0, 1, 2]
This means:
- For query at time 10: 0 servers were inactive in interval [5, 10]
- For query at time 6: 1 server was inactive in interval [1, 6]
- For query at time 4: 2 servers were inactive in interval [-1, 4]
The key efficiency comes from the sliding window - we process each log entry at most twice (once when entering the window, once when leaving), and we maintain the active server count incrementally rather than recalculating from scratch for each query.
Solution Implementation
1class Solution:
2 def countServers(
3 self, n: int, logs: List[List[int]], x: int, queries: List[int]
4 ) -> List[int]:
5 """
6 Count the number of servers that received no requests within specified time windows.
7
8 Args:
9 n: Total number of servers (numbered from 1 to n)
10 logs: List of [server_id, time] representing server requests
11 x: Time window size
12 queries: List of query times, for each we check window [query-x, query]
13
14 Returns:
15 List of integers where each element represents the count of servers
16 with no requests in the corresponding time window
17 """
18 from collections import Counter
19 from itertools import count
20 from typing import List
21
22 # Counter to track active servers in current window
23 server_count = Counter()
24
25 # Sort logs by timestamp for efficient window processing
26 logs.sort(key=lambda log: log[1])
27
28 # Initialize result array with same length as queries
29 result = [0] * len(queries)
30
31 # Two pointers for sliding window technique
32 left_pointer = 0 # Points to the start of the current window
33 right_pointer = 0 # Points to the end of the current window
34
35 # Process queries in sorted order while maintaining original indices
36 # zip(queries, count()) pairs each query with its original index
37 for query_time, original_index in sorted(zip(queries, count())):
38 # Calculate window boundaries
39 window_start = query_time - x
40 window_end = query_time
41
42 # Expand window right: add all logs with timestamp <= window_end
43 while right_pointer < len(logs) and logs[right_pointer][1] <= window_end:
44 server_id = logs[right_pointer][0]
45 server_count[server_id] += 1
46 right_pointer += 1
47
48 # Shrink window left: remove all logs with timestamp < window_start
49 while left_pointer < len(logs) and logs[left_pointer][1] < window_start:
50 server_id = logs[left_pointer][0]
51 server_count[server_id] -= 1
52 # Remove server from counter if count reaches 0
53 if server_count[server_id] == 0:
54 server_count.pop(server_id)
55 left_pointer += 1
56
57 # Calculate servers with no requests (total servers - active servers)
58 result[original_index] = n - len(server_count)
59
60 return result
61
1class Solution {
2 public int[] countServers(int n, int[][] logs, int x, int[] queries) {
3 // Sort logs by timestamp in ascending order
4 Arrays.sort(logs, (a, b) -> a[1] - b[1]);
5
6 int queryCount = queries.length;
7
8 // Create array of [queryValue, originalIndex] pairs to maintain original query order
9 int[][] queriesWithIndex = new int[queryCount][2];
10 for (int i = 0; i < queryCount; ++i) {
11 queriesWithIndex[i] = new int[] {queries[i], i};
12 }
13
14 // Sort queries by value in ascending order
15 Arrays.sort(queriesWithIndex, (a, b) -> a[0] - b[0]);
16
17 // Map to track count of requests per server within current window
18 Map<Integer, Integer> serverRequestCount = new HashMap<>();
19
20 // Result array to store answers
21 int[] result = new int[queryCount];
22
23 // Two pointers for sliding window
24 int leftPointer = 0; // Points to start of window
25 int rightPointer = 0; // Points to end of window
26
27 // Process each query in sorted order
28 for (int[] currentQuery : queriesWithIndex) {
29 int rightBound = currentQuery[0];
30 int originalIndex = currentQuery[1];
31 int leftBound = rightBound - x;
32
33 // Add all logs with timestamp <= rightBound to the window
34 while (rightPointer < logs.length && logs[rightPointer][1] <= rightBound) {
35 int serverId = logs[rightPointer][0];
36 serverRequestCount.merge(serverId, 1, Integer::sum);
37 rightPointer++;
38 }
39
40 // Remove all logs with timestamp < leftBound from the window
41 while (leftPointer < logs.length && logs[leftPointer][1] < leftBound) {
42 int serverId = logs[leftPointer][0];
43 // Decrement count and remove server if count becomes 0
44 if (serverRequestCount.merge(serverId, -1, Integer::sum) == 0) {
45 serverRequestCount.remove(serverId);
46 }
47 leftPointer++;
48 }
49
50 // Calculate servers with no requests in window [leftBound, rightBound]
51 // Total servers - servers that received at least one request
52 result[originalIndex] = n - serverRequestCount.size();
53 }
54
55 return result;
56 }
57}
58
1class Solution {
2public:
3 vector<int> countServers(int n, vector<vector<int>>& logs, int x, vector<int>& queries) {
4 // Sort logs by timestamp in ascending order
5 sort(logs.begin(), logs.end(), [](const auto& a, const auto& b) {
6 return a[1] < b[1];
7 });
8
9 int numQueries = queries.size();
10
11 // Create pairs of (query_value, original_index) to maintain original order after sorting
12 vector<pair<int, int>> queryPairs(numQueries);
13 for (int i = 0; i < numQueries; ++i) {
14 queryPairs[i] = {queries[i], i};
15 }
16
17 // Sort queries by value in ascending order
18 sort(queryPairs.begin(), queryPairs.end());
19
20 // Map to count requests per server within the time window
21 unordered_map<int, int> serverRequestCount;
22
23 // Result array to store answers in original query order
24 vector<int> result(numQueries);
25
26 // Two pointers for sliding window
27 int leftPointer = 0; // Points to the start of the time window
28 int rightPointer = 0; // Points to the end of the time window
29
30 // Process each query in sorted order
31 for (auto& [queryTime, originalIndex] : queryPairs) {
32 // Calculate the left boundary of the time window [queryTime - x, queryTime]
33 int windowStart = queryTime - x;
34
35 // Add all logs with timestamp <= queryTime to the window
36 while (rightPointer < logs.size() && logs[rightPointer][1] <= queryTime) {
37 int serverId = logs[rightPointer][0];
38 ++serverRequestCount[serverId];
39 ++rightPointer;
40 }
41
42 // Remove all logs with timestamp < windowStart from the window
43 while (leftPointer < logs.size() && logs[leftPointer][1] < windowStart) {
44 int serverId = logs[leftPointer][0];
45 --serverRequestCount[serverId];
46
47 // Remove server from map if it has no requests in the window
48 if (serverRequestCount[serverId] == 0) {
49 serverRequestCount.erase(serverId);
50 }
51 ++leftPointer;
52 }
53
54 // Calculate number of servers with no requests in the time window
55 // Total servers - servers with at least one request
56 result[originalIndex] = n - serverRequestCount.size();
57 }
58
59 return result;
60 }
61};
62
1/**
2 * Counts the number of servers that received no requests during specified time windows
3 * @param n - Total number of servers (numbered from 1 to n)
4 * @param logs - Array of [serverId, time] pairs representing server requests
5 * @param x - Time window duration
6 * @param queries - Array of query times to check
7 * @returns Array containing count of servers with no requests for each query
8 */
9function countServers(n: number, logs: number[][], x: number, queries: number[]): number[] {
10 // Sort logs by timestamp in ascending order
11 logs.sort((a: number[], b: number[]) => a[1] - b[1]);
12
13 const queryCount: number = queries.length;
14
15 // Create array of [queryTime, originalIndex] pairs to maintain original order after sorting
16 const queriesWithIndex: number[][] = [];
17 for (let i = 0; i < queryCount; ++i) {
18 queriesWithIndex.push([queries[i], i]);
19 }
20
21 // Sort queries by time in ascending order
22 queriesWithIndex.sort((a: number[], b: number[]) => a[0] - b[0]);
23
24 // Map to track count of requests per server in current window
25 const serverRequestCount: Map<number, number> = new Map();
26
27 // Initialize result array
28 const result: number[] = new Array(queryCount);
29
30 // Pointer for start of time window
31 let windowStart: number = 0;
32 // Pointer for end of time window
33 let windowEnd: number = 0;
34
35 // Process each query in sorted order
36 for (const [queryTime, originalIndex] of queriesWithIndex) {
37 // Calculate the start time of the window [queryTime - x, queryTime]
38 const windowStartTime: number = queryTime - x;
39
40 // Add all logs that fall within the window end time
41 while (windowEnd < logs.length && logs[windowEnd][1] <= queryTime) {
42 const serverId: number = logs[windowEnd][0];
43 serverRequestCount.set(serverId, (serverRequestCount.get(serverId) || 0) + 1);
44 ++windowEnd;
45 }
46
47 // Remove all logs that fall before the window start time
48 while (windowStart < logs.length && logs[windowStart][1] < windowStartTime) {
49 const serverId: number = logs[windowStart][0];
50 const currentCount: number = (serverRequestCount.get(serverId) || 0) - 1;
51
52 if (currentCount === 0) {
53 // Remove server from map if no requests remain in window
54 serverRequestCount.delete(serverId);
55 } else {
56 serverRequestCount.set(serverId, currentCount);
57 }
58 ++windowStart;
59 }
60
61 // Calculate servers with no requests: total servers - servers with requests
62 result[originalIndex] = n - serverRequestCount.size;
63 }
64
65 return result;
66}
67
Time and Space Complexity
Time Complexity: O(l × log l + m × log m + l + m)
which simplifies to O(l × log l + m × log m)
where l
is the length of the logs array and m
is the length of the queries array.
The analysis breaks down as follows:
- Sorting logs by timestamp:
O(l × log l)
- Sorting queries with their indices using
sorted(zip(queries, count()))
:O(m × log m)
- Processing each query:
O(m)
iterations- The inner while loops for expanding and shrinking the window iterate through each log at most once across all queries:
O(l)
total
- The inner while loops for expanding and shrinking the window iterate through each log at most once across all queries:
- Total:
O(l × log l + m × log m + l + m)
=O(l × log l + m × log m)
Space Complexity: O(l + m)
where l
is the length of the logs array and m
is the length of the queries array.
The space usage includes:
- The
cnt
Counter dictionary which can store at mostn
server IDs (wheren
≤l
since each log corresponds to a server):O(n)
orO(l)
in worst case - The sorted queries with indices (from
sorted(zip(queries, count()))
):O(m)
- The
ans
array to store results:O(m)
- Total:
O(l + m)
Note: The reference answer includes n
in the time complexity formula, but n
only appears in the computation n - len(cnt)
which is O(1)
per query, so it doesn't affect the overall asymptotic complexity.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Window Boundary Handling
A critical pitfall is misunderstanding the inclusive nature of the time window [queries[i] - x, queries[i]]
. Developers often make mistakes with the boundary conditions when moving the pointers.
Incorrect Implementation:
# Wrong: Using <= for left boundary removal
while left_pointer < len(logs) and logs[left_pointer][1] <= window_start:
server_count[logs[left_pointer][0]] -= 1
# ... rest of removal logic
Why it's wrong: This removes logs that are exactly at window_start
, but the window is inclusive on both ends. Logs at time window_start
should remain in the window.
Correct Implementation:
# Correct: Using < for left boundary removal
while left_pointer < len(logs) and logs[left_pointer][1] < window_start:
server_count[logs[left_pointer][0]] -= 1
# ... rest of removal logic
2. Server Numbering Confusion
Another common mistake is confusion about server numbering. The problem states servers are numbered from 0
to n-1
, but developers might assume they're numbered from 1
to n
.
Incorrect Assumption:
# Wrong: Assuming servers are 1-indexed total_servers = n + 1 # This would be wrong # Or checking if server_id is valid if server_id < 1 or server_id > n: # Wrong bounds check
Correct Understanding:
- Servers are numbered: 0, 1, 2, ..., n-1
- Total number of servers is exactly
n
- Valid server IDs range from 0 to n-1
3. Processing Queries in Wrong Order
A subtle but critical error is forgetting to restore the original query order after sorting.
Incorrect Implementation:
# Wrong: Sorting queries but losing original order queries.sort() result = [] for query_time in queries: # Process query result.append(inactive_servers) return result # Results are in sorted query order, not original order!
Correct Implementation:
# Correct: Maintain original indices while sorting
for query_time, original_index in sorted(zip(queries, count())):
# Process query
result[original_index] = inactive_servers # Store at original position
4. Counter Cleanup Memory Leak
Failing to remove servers with zero count from the Counter can lead to incorrect results and unnecessary memory usage.
Incorrect Implementation:
# Wrong: Only decrementing without cleanup
while left_pointer < len(logs) and logs[left_pointer][1] < window_start:
server_count[logs[left_pointer][0]] -= 1
left_pointer += 1
# Missing: removal of zero-count entries
Why it's wrong: Servers with count 0 would still be counted in len(server_count)
, leading to incorrect inactive server calculations.
Correct Implementation:
# Correct: Remove servers with zero count
while left_pointer < len(logs) and logs[left_pointer][1] < window_start:
server_id = logs[left_pointer][0]
server_count[server_id] -= 1
if server_count[server_id] == 0:
server_count.pop(server_id) # Critical: remove from counter
left_pointer += 1
5. Edge Case: Empty Logs or Queries
Not handling edge cases where logs or queries might be empty.
Solution:
# Add early returns for edge cases
if not queries:
return []
if not logs:
return [n] * len(queries) # All servers inactive for all queries
Which technique can we use to find the middle of a linked list?
Recommended Readings
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
https assets algo monster cover_photos stack svg Sliding Window Maximum Monotonic Stack We have an array and a sliding window defined by a start index and an end index The sliding window moves from left of the array to right There are always k elements in the window The window
Coding Interview 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
Want a Structured Path to Master System Design Too? Don’t Miss This!