1606. Find Servers That Handled Most Number of Requests
Problem Description
In this LeetCode problem, we are given k
servers, each capable of handling only one request at a time. Multiple requests, represented by their arrival times and loads (duration to complete), arrive and need to be assigned to these servers. The mechanism for assigning requests to servers follows a specific algorithm:
- The
i
-th request arrives. - If all servers are currently busy, the request is dropped.
- If the server at index
(i % k)
is available, the request is assigned there. - If that server is busy, we proceed to the next server in a round-robin fashion until we find an available server or conclude that all are busy.
The goal is to find which server(s) gets the most requests. The returned list should contain the ID(s) of the busiest server(s).
Intuition
For the solution, we need efficient ways to track available and busy servers, along with the count of handled requests. Here is the intuition broken down:
-
Use a data structure to keep track of free servers that allows for efficient retrieval and insertion based on server indices. A
SortedList
can be appropriate here as it maintains the elements in a sorted order. -
Have a way to keep track of the busy servers along with the time they will become free. A min-heap, where the key is the time when the server gets free, can serve this purpose because it can quickly tell us the next server that will become free.
-
Maintain a list to count the number of requests each server has handled. This will help in identifying the busiest server(s).
-
Iterate over each request, updating the free and busy lists accordingly:
- Make any server that has finished its load free.
- If there is a free server, assign the request to the server that follows the given criteria.
- Update the count of requests handled by the chosen server.
- If no free server is found, drop the request.
-
Finding the index of the free server that comes after
(i % k)
can be done using binary search in theSortedList
. If the index is out of range, we start from the beginning of the list (ensuring the round-robin behavior). -
After evaluating all requests, identify the maximum number of requests handled by any server. Then, compile a list of all servers that achieved this maximum.
The provided solution implements these steps, efficiently managing requests and server availability to conclude which servers were the busiest.
Learn more about Greedy and Heap (Priority Queue) patterns.
Solution Approach
The implementation of the solution uses Python's SortedList
from the sortedcontainers
module and a min-heap to manage the efficient tracking of free and busy servers. The steps followed in the implementation are as follows:
-
Initialize a
SortedList
namedfree
with all server indices from0
tok-1
to represent all servers being free initially. -
Create an empty list
busy
which will function as a min-heap to track busy servers and when they will be free. Each element inbusy
is a tuple consisting of the time the server will be free and the server index. -
cnt
is an array of zeros with lengthk
which will be used to count the number of requests each server has successfully handled. -
Loop through each request by enumerating over paired
arrival
andload
values. -
Inside the loop, first deal with servers that have completed their requests:
- While there is a server in the
busy
list whose end time is less than or equal to the current request's start time, remove and add it tofree
servers. This operation is done usingheappop
which removes the smallest element from thebusy
list.
- While there is a server in the
-
If there are no
free
servers, the current request is dropped, as per the algorithm. Continue to the next iteration. -
Otherwise, find the correct server:
- Use
free.bisect_left(i % k)
to find the position infree
to inserti % k
and maintain sorted order. Ifj
is out of range (meaning no free servers with index greater than or equal toi % k
), wrap around to the start of the list (j=0
).
- Use
-
Once the server is identified, increment the corresponding server's handled request count in
cnt
. -
Push the end time of the current request, along with the server index, to the
busy
list usingheappush
, so it's now considered busy until that time. Remove the server fromfree
. -
After processing all requests, determine the maximum number of handled requests by any server.
-
Iterate over
cnt
and collect the indices corresponding to this maximum number of requests.
The implementation closely follows the initial intuition and uses Python's built-in data structures to efficiently manage the problem's constraints. By doing so, the solution avoids brute-force approaches and can handle large sets of data within the required time constraints.
Combining these data structures with linear processing of requests (aided by logarithmic operations for adding/removing servers from sorted structures) allows the algorithm to remain scalable and performant.
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 walk through a small example to illustrate the solution approach.
Assume we have k=3
servers and a list of requests where each request is represented as a tuple of (arrival time, load)
:
Requests: [(1,4), (2,3), (3,4), (4,2)]
Steps:
- Initialize
free = SortedList([0, 1, 2])
, as all servers are free initially. busy = []
is initially empty since no servers are busy at the start.cnt = [0, 0, 0]
is used to count the number of requests handled by each server.
Now, let's iterate over each request:
Request (1,4):
- Server 0 (as
1%3=1
) is free, assign this request to Server 0. - Update
cnt
to[1, 0, 0]
. - Add Server 0 with the time it will be free
(1+4=5)
tobusy
. - Update
free
to[1, 2]
.
Request (2,3):
- Server 1 (as
2%3=2
) is free, assign this request to Server 1. - Update
cnt
to[1, 1, 0]
. - Add Server 1 with the time it will be free
(2+3=5)
tobusy
. - Update
free
to[2]
.
Request (3,4):
- Server 2 (as
3%3=0
) is free, assign this request to Server 2. - Update
cnt
to[1, 1, 1]
. - Add Server 2 with the time it will be free
(3+4=7)
tobusy
. - Update
free
to[]
.
Request (4,2):
- Check busy servers to see if any have become free. None have, because the current arrival time
4
is not greater than5
or7
. - All servers are busy (
free
is empty), so this request is dropped.
After all requests:
busy
eventually clears as time progresses, adding servers back tofree
.- Final
cnt
is[1, 1, 1]
.
Conclusion:
- The maximum number of handled requests by any server is
1
. - All servers handled exactly
1
request, so they are all the busiest.
According to our approach, the output would be [0, 1, 2]
, as these are the indices of servers with the maximum count of handled requests. The implemented solution effectively tracked servers' statuses and requests, resulting in an efficient and scalable algorithm.
Solution Implementation
1from sortedcontainers import SortedList
2from heapq import heappush, heappop
3
4class Solution:
5 def busiestServers(self, server_count: int, arrival_times: list, process_time: list) -> list:
6 # Initialize a sorted list with server indices
7 available_servers = SortedList(range(server_count))
8
9 # List to keep track of servers that are currently busy
10 busy_servers = []
11
12 # Counter for the number of times each server has been used
13 request_count = [0] * server_count
14
15 # Iterate over all arrival times and corresponding processing times
16 for i, (arrival_time, duration) in enumerate(zip(arrival_times, process_time)):
17
18 # Free up servers that have finished processing according to current arrival time
19 while busy_servers and busy_servers[0][0] <= arrival_time:
20 finished_server = busy_servers[0][1]
21 available_servers.add(finished_server)
22 heappop(busy_servers)
23
24 # If there are no free servers, skip to next request
25 if not available_servers:
26 continue
27
28 # Find an available server starting from the server with index i modulo server_count
29 server_index = available_servers.bisect_left(i % server_count)
30
31 # If the index is beyond the last server, wrap around to the first server
32 if server_index == len(available_servers):
33 server_index = 0
34
35 # Assign selected server for current request
36 assigned_server = available_servers[server_index]
37
38 # Increase the count of the requests handled by the server
39 request_count[assigned_server] += 1
40
41 # Mark the server as busy by scheduling its next free time
42 heappush(busy_servers, (arrival_time + duration, assigned_server))
43
44 # Remove the server from available servers since it is now busy
45 available_servers.remove(assigned_server)
46
47 # Find the maximum number of requests handled by a server
48 max_requests = max(request_count)
49
50 # Determine all servers that have handled the maximum number of requests
51 busiest_servers = [server for server, count in enumerate(request_count) if count == max_requests]
52
53 return busiest_servers
54
1class Solution {
2 public List<Integer> busiestServers(int k, int[] arrival, int[] load) {
3 // Count array to keep track of requests handled by each server
4 int[] count = new int[k];
5 // Priority queue to manage currently busy servers (end time, server index)
6 PriorityQueue<int[]> busyServers = new PriorityQueue<>((a, b) -> a[0] - b[0]);
7 // TreeSet to manage available servers
8 TreeSet<Integer> availableServers = new TreeSet<>();
9 for (int i = 0; i < k; ++i) {
10 availableServers.add(i);
11 }
12 for (int i = 0; i < arrival.length; ++i) {
13 int startTime = arrival[i];
14 int endTime = startTime + load[i];
15 // Release servers that have completed their tasks
16 while (!busyServers.isEmpty() && busyServers.peek()[0] <= startTime) {
17 availableServers.add(busyServers.poll()[1]);
18 }
19 // Continue to next request if no servers are available
20 if (availableServers.isEmpty()) {
21 continue;
22 }
23 // Find the server with the closest index after i % k
24 Integer serverIndex = availableServers.ceiling(i % k);
25 // If no such server is found, get the first available server
26 if (serverIndex == null) {
27 serverIndex = availableServers.first();
28 }
29 // Increment the request count for this server
30 ++count[serverIndex];
31 // Add server to busy servers with corresponding end time
32 busyServers.offer(new int[] {endTime, serverIndex});
33 // Remove this server from the set of available servers
34 availableServers.remove(serverIndex);
35 }
36 // Find the max value in the count array
37 int maxCount = 0;
38 for (int value : count) {
39 maxCount = Math.max(maxCount, value);
40 }
41 // Collect all servers that have this max request count
42 List<Integer> busiestServers = new ArrayList<>();
43 for (int i = 0; i < k; ++i) {
44 if (count[i] == maxCount) {
45 busiestServers.add(i);
46 }
47 }
48 return busiestServers;
49 }
50}
51
1#include <vector>
2#include <set>
3#include <queue>
4#include <algorithm>
5
6class Solution {
7public:
8 vector<int> busiestServers(int k, vector<int>& arrival, vector<int>& load) {
9 // Initialize a set to keep track of available servers
10 set<int> availableServers;
11 for (int i = 0; i < k; ++i) {
12 availableServers.insert(i);
13 }
14
15 // Priority queue to manage busy servers based on their end time
16 priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> busyServers;
17
18 // Vector to hold the count of requests handled by each server
19 vector<int> requestCount(k, 0);
20
21 // Iterate over each request
22 for (int i = 0; i < arrival.size(); ++i) {
23 int startTime = arrival[i];
24 int endTime = startTime + load[i];
25
26 // Free up servers that have completed their tasks
27 while (!busyServers.empty() && busyServers.top().first <= startTime) {
28 availableServers.insert(busyServers.top().second);
29 busyServers.pop();
30 }
31
32 // If there are no available servers, skip to the next request
33 if (availableServers.empty()) {
34 continue;
35 }
36
37 // Find the first server that can take the current request
38 auto serverIterator = availableServers.lower_bound(i % k);
39 if (serverIterator == availableServers.end()) {
40 serverIterator = availableServers.begin();
41 }
42
43 // Assign the task to the server
44 int assignedServer = *serverIterator;
45 ++requestCount[assignedServer];
46 busyServers.emplace(endTime, assignedServer);
47
48 // Remove the server from the available set as it's now busy
49 availableServers.erase(assignedServer);
50 }
51
52 // Find the maximum number of requests handled by any server
53 int maxRequests = *max_element(requestCount.begin(), requestCount.end());
54
55 // Collect all servers that handled the max number of requests
56 vector<int> busiestServers;
57 for (int i = 0; i < k; ++i) {
58 if (requestCount[i] == maxRequests) {
59 busiestServers.push_back(i);
60 }
61 }
62
63 return busiestServers;
64 }
65};
66
1// Importing the standard libraries for data structures and algorithms
2import { PriorityQueue } from 'typescript-collections';
3
4// Define a Pair class to hold pairs of values
5class Pair<T, U> {
6 constructor(public first: T, public second: U) {}
7}
8
9// Define a Comparator interface for priority queue
10interface Comparator<T> {
11 compare(o1: T, o2: T): number;
12}
13
14// Define a function to find the busiest servers
15function busiestServers(k: number, arrival: number[], load: number[]): number[] {
16 // Initialize a set to keep track of available servers
17 const availableServers = new Set<number>();
18 for (let i = 0; i < k; ++i) {
19 availableServers.add(i);
20 }
21
22 // Function to compare pairs for priority queue
23 const pairComparator: Comparator<Pair<number, number>> = {
24 compare: (o1, o2) => {
25 if (o1.first !== o2.first) {
26 return o1.first - o2.first;
27 }
28 return o1.second - o2.second;
29 },
30 };
31
32 // Initialize a priority queue to manage busy servers based on their end time
33 const busyServers = new PriorityQueue(pairComparator);
34
35 // Array to hold the count of requests handled by each server
36 const requestCount: number[] = new Array(k).fill(0);
37
38 // Iterate over each request
39 arrival.forEach((startTime, i) => {
40 const endTime = startTime + load[i];
41
42 // Free up servers that have completed their tasks
43 while (!busyServers.isEmpty() && busyServers.peek().first <= startTime) {
44 availableServers.add(busyServers.dequeue().second);
45 }
46
47 // If there are no available servers, skip to the next request
48 if (availableServers.size === 0) {
49 return;
50 }
51
52 // Find the first server that can take the current request
53 let assignedServer = -1;
54 for (let j = 0; j < k; j++) {
55 const serverId = (i + j) % k;
56 if (availableServers.has(serverId)) {
57 assignedServer = serverId;
58 break;
59 }
60 }
61
62 // Assign the task to the server if one is found
63 if (assignedServer !== -1) {
64 requestCount[assignedServer]++;
65 busyServers.enqueue(new Pair(endTime, assignedServer));
66
67 // Remove the server from the available set as it's now busy
68 availableServers.delete(assignedServer);
69 }
70 });
71
72 // Find the maximum number of requests handled by any server
73 const maxRequests = Math.max(...requestCount);
74
75 // Collect all servers that handled the max number of requests
76 const busiestServers: number[] = [];
77 requestCount.forEach((count, index) => {
78 if (count === maxRequests) {
79 busiestServers.push(index);
80 }
81 });
82
83 return busiestServers;
84}
85
86// Please note that you might need to implement or import a PriorityQueue
87// and ensure the TypeScript environment is setup with all necessary typings.
88
Time and Space Complexity
Time Complexity
The time complexity of the code consists of several parts:
- Initialization: Initializing the
free
sorted list withrange(k)
takesO(k * log(k))
because it's creating a sorted list ofk
servers. - Processing requests: The
for
loop runsn
times wheren
is the number of requests (the length ofarrival
). - Freeing up servers: The inner
while
loop pops elements from the heapbusy
which has at mostk
elements. Eachheappop
operation takesO(log(k))
time. In the worst case, all servers are freed in one iteration so the complexity can beO(k * log(k))
but averaged overn
iterations it doesn't exceedn * log(k)
adding to the total complexity. - Finding the server: Using
bisect_left
on theSortedList
has a time complexity ofO(log(k))
for searching. This operation is executed once per request. - Updating the heap and list:
heappush
intobusy
takesO(log(k))
and removing an element fromfree
takesO(log(k))
as well.
Considering these parts together:
Initialization is O(k * log(k))
.
The for
loop runs n
times, within it:
- The freeing of servers is
O(n * log(k))
overn
iterations. - Finding the server is
O(log(k))
for each request, soO(n * log(k))
in total. - Updating the heap and list is
O(2 * log(k))
for each request, resulting inO(n * log(k))
.
So the total time complexity is O(k * log(k) + n * log(k) + n * log(k) + n * log(k))
, which simplifies to O((k + 3n) * log(k))
.
Space Complexity
The space complexity is determined by the space required for:
- SortedList
free
: which holds at mostk
elements, so it isO(k)
. - Heap
busy
: in the worst case, it can holdk
elements simultaneously, so it isO(k)
. - List
cnt
: it is a list of lengthk
, henceO(k)
. - Output list: in the worst case where all servers have the maximum count, the output list can also have
k
elements, so it isO(k)
.
The space complexity is therefore the sum of all these parts, which is O(k) + O(k) + O(k) + O(k)
, which simplifies to O(k)
.
Learn more about how to find time and space complexity quickly using problem constraints.
A heap is a ...?
Recommended Readings
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
https algomonster s3 us east 2 amazonaws com cover_photos heap svg Priority Queue and Heap What is the relationship between priority queue and heap Priority Queue is an Abstract Data Type and Heap is the concrete data structure we use to implement a priority queue Priority Queue A priority queue
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!