1882. Process Tasks Using Servers - Explanation
Prerequisites
Before attempting this problem, you should be comfortable with:
- Heap (Priority Queue) - Two min-heaps are used to efficiently track available servers (by weight/index) and busy servers (by finish time)
- Simulation - Understanding how to model time-based task assignment and server availability
- Custom Comparators - Implementing multi-key ordering (weight, then index) for priority queue elements
1. Brute Force (Simulation)
Intuition
We simulate the process step by step. For each task, we advance time to when the task becomes available, mark all servers that have finished as available, then pick the best available server (lowest weight, then lowest index). If no server is free, we wait until the earliest one finishes. This direct simulation is easy to understand but slow due to repeated linear scans.
Algorithm
- Initialize arrays to track each server's availability status and finish time.
- For each task at index
t:- Set
timeto at leastt(the task's arrival time). - Mark servers as available if their finish time is at or before current
time. - If no servers are available, advance
timeto the earliest finish time and update availability. - Find the available server with the smallest weight (and smallest index for ties).
- Assign the task to that server, mark it unavailable, and set its finish time.
- Set
- Return the list of assigned server indices.
class Solution:
def assignTasks(self, servers: List[int], tasks: List[int]) -> List[int]:
n, m = len(servers), len(tasks)
available = [True] * n
finishTime = [0] * n
res = []
time = 0
for t in range(m):
time = max(time, t)
for i in range(n):
if finishTime[i] <= time:
available[i] = True
if not any(available):
time = min(finishTime)
for i in range(n):
if finishTime[i] <= time:
available[i] = True
minIdx = -1
for i in range(n):
if (available[i] and
(minIdx == -1 or servers[i] < servers[minIdx] or
(servers[i] == servers[minIdx] and i < minIdx))
):
minIdx = i
res.append(minIdx)
available[minIdx] = False
finishTime[minIdx] = time + tasks[t]
return resTime & Space Complexity
- Time complexity:
- Space complexity:
- extra space.
- space for the output array.
Where is the number of tasks and is the number of servers.
2. Two Min-Heaps - I
Intuition
Using two heaps makes server selection efficient. One heap holds available servers ordered by (weight, index), and another holds unavailable servers ordered by their finish time. When processing a task, we move servers from unavailable to available if their time has passed, then pop the best server from the available heap.
Algorithm
- Create two heaps:
available: min-heap ordered by (weight,index) for free servers.unavailable: min-heap ordered by finishtimefor busy servers.
- Add all servers to
available. - For each task at index
i:- Set
timeto at leasti. - If no server is available, advance
timeto the earliest finishtimeinunavailable. - Move all servers from
unavailablewhose finishtimeis at or beforetimetoavailable. - Pop the best server from
available, assign the task, and push it tounavailablewith its new finishtime.
- Set
- Return the result array.
class Solution:
def assignTasks(self, servers: List[int], tasks: List[int]) -> List[int]:
res = [0] * len(tasks)
available = [(servers[i], i) for i in range(len(servers))]
heapq.heapify(available)
unavailable = []
t = 0
for i in range(len(tasks)):
t = max(t, i)
if not available:
t = unavailable[0][0]
while unavailable and t >= unavailable[0][0]:
timeFree, weight, index = heapq.heappop(unavailable)
heapq.heappush(available, (weight, index))
weight, index = heapq.heappop(available)
res[i] = index
heapq.heappush(unavailable, (t + tasks[i], weight, index))
return resTime & Space Complexity
- Time complexity:
- Space complexity:
- extra space.
- space for the output array.
Where is the number of tasks and is the number of servers.
3. Two Min-Heaps - II
Intuition
This is a variation of the two-heap approach with slightly different state tracking. Instead of storing finish time separately, we embed the last known free time within the available heap entries. When transferring servers between heaps, we update this time accordingly. The logic remains the same: efficiently pick the best available server using heaps.
Algorithm
- Create two heaps:
available: min-heap storing (weight,index,timeFree) for available servers.unavailable: min-heap storing (timeFree,weight,index) for busy servers.
- Add all servers to
availablewith initialtimeFreeof0. - For each task at index
i:- Move servers from
unavailabletoavailablewhile their finishtimeis at or beforei, or whileavailableis empty (must wait for a server). - Pop the best server from
available. - Assign the task and push the server to
unavailablewith updated finishtime:max(timeFree, i) + task duration.
- Move servers from
- Return the result array.
class Solution:
def assignTasks(self, servers: List[int], tasks: List[int]) -> List[int]:
res = []
available = [[weight, i, 0] for i, weight in enumerate(servers)]
unavailable = []
heapq.heapify(available)
for i, task in enumerate(tasks):
while unavailable and unavailable[0][0] <= i or not available:
timeFree, weight, index = heapq.heappop(unavailable)
heapq.heappush(available, [weight, index, timeFree])
weight, index, timeFree = heapq.heappop(available)
res.append(index)
heapq.heappush(unavailable, [max(timeFree, i) + task, weight, index])
return resTime & Space Complexity
- Time complexity:
- Space complexity:
- extra space.
- space for the output array.
Where is the number of tasks and is the number of servers.
Common Pitfalls
Incorrect Heap Ordering for Server Selection
When no servers are available at time t, you must wait until the earliest server becomes free. A common mistake is not correctly ordering the unavailable heap by finish time, or forgetting to move all servers that become free at that time back to the available heap. Multiple servers might become available simultaneously.
Not Advancing Time When All Servers Are Busy
If all servers are busy when a task arrives, you must jump time forward to when the next server becomes free. Forgetting this causes an infinite loop or incorrect assignments. The time should be set to unavailable.peek().finishTime, not incremented by 1.
Wrong Tie-Breaking Logic in Priority Queue
The problem requires selecting the server with the smallest weight, and among ties, the smallest index. Implementing the comparator incorrectly (e.g., comparing indices before weights) leads to wrong server assignments. Ensure the primary comparison is on weight and secondary on index.