Before attempting this problem, you should be comfortable with:
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.
t:time to at least t (the task's arrival time).time.time to the earliest finish time and update availability.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 resWhere is the number of tasks and is the number of servers.
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.
available: min-heap ordered by (weight, index) for free servers.unavailable: min-heap ordered by finish time for busy servers.available.i:time to at least i.time to the earliest finish time in unavailable.unavailable whose finish time is at or before time to available.available, assign the task, and push it to unavailable with its new finish time.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 resWhere is the number of tasks and is the number of servers.
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.
available: min-heap storing (weight, index, timeFree) for available servers.unavailable: min-heap storing (timeFree, weight, index) for busy servers.available with initial timeFree of 0.i:unavailable to available while their finish time is at or before i, or while available is empty (must wait for a server).available.unavailable with updated finish time: max(timeFree, i) + task duration.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 resWhere is the number of tasks and is the number of servers.
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.
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.
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.