Process Tasks Using Servers

MediumHeap · Simulation · Two Priority Queues🧩 Heap & Top-KLeetCode #1882 ↗Updated

Problem

Server i has weight servers[i]; task j needs tasks[j] seconds and joins a FIFO queue at second j. Whenever a server is idle and the queue is non-empty, the front task goes to the idle server with the smallest weight (ties: smallest index). If none is idle, tasks wait until one frees up. Return ans where ans[j] is the server that ran task j.
Inputservers = [3,3,2], tasks = [1,2,3,2,1,2]Output[2,2,0,2,1,2]
Inputservers = [5,1,4,3,2], tasks = [2,1,2,4,5,2,1]Output[1,4,1,4,1,3,2]

1 ≤ n, m ≤ 2·105  ·  1 ≤ servers[i], tasks[j] ≤ 2·105  ·  finish times can exceed 231

Try it yourself

Java
class Solution {
    public int[] assignTasks(int[] servers, int[] tasks) {
        
    }
}
Your code is saved in this browser.Submit on LeetCode ↗
Key insight
Keep two heaps: idle servers ordered by (weight, index) and busy servers ordered by finish time. Before each assignment, move every server that has finished by the current time from busy to idle; if idle is empty, jump the clock to the earliest finish time.
PatternHeap & Top-K

Instinct "K largest / smallest / closest / most frequent", or "always take the best available next"? Keep a heap - size k for top-K, or a ready-queue for scheduling.

Memory hack Guard the top-K club with a bouncer - a min-heap of size k whose root is the weakest member; anyone stronger kicks them out.

Open the Heap & Top-K guide →

Approaches

Brute ForceLinear Scan of All Servers per Task

Time O(m · n)Space O(n)

Track freeAt[i] for each server. For task j, the clock is max(clock, j); scan all servers for the lightest one with freeAt ≤ clock. If none, set the clock to the minimum freeAt and scan again. Tasks start in queue order, so the clock never moves backwards.

Java
public int[] assignTasks(int[] servers, int[] tasks) {
    int n = servers.length, m = tasks.length;
    long[] freeAt = new long[n];
    int[] ans = new int[m];
    long clock = 0;
    for (int j = 0; j < m; j++) {
        clock = Math.max(clock, j);
        int pick = choose(servers, freeAt, clock);
        if (pick == -1) { // everyone busy: wait for the earliest finisher
            long next = Long.MAX_VALUE;
            for (long f : freeAt) next = Math.min(next, f);
            clock = next;
            pick = choose(servers, freeAt, clock);
        }
        ans[j] = pick;
        freeAt[pick] = clock + tasks[j];
    }
    return ans;
}

private int choose(int[] servers, long[] freeAt, long clock) {
    int pick = -1;
    for (int i = 0; i < servers.length; i++) {
        // strict < keeps the smaller index on equal weight
        if (freeAt[i] <= clock && (pick == -1 || servers[i] < servers[pick])) pick = i;
    }
    return pick;
}

Obviously faithful to the rules, but 4·1010 steps at the limits.

At a glance

LevelApproachTimeSpace
Brute ForceLinear Scan of All Servers per TaskO(m · n)O(n)
OptimalIdle Heap + Busy Heap with Clock JumpsO((n + m) log n)O(n)
Common pitfalls
  • Using int for finish times: 2·105 tasks × 2·105 s overflows.
  • Starting task j at second j even when it had to queue — the clock must be monotone.
  • Releasing only one finished server per step, so a lighter one finishing at the same time is ignored.
Interview tips
  • Clarify the queue semantics: tasks are strictly FIFO, a waiting task blocks the ones behind it.
  • Name both heaps and their orderings before writing comparators.
  • Point out the clock-jump: never tick second by second, the times can be ~1010.
Pattern
Two heaps (available / in-use) → resource scheduling where resources free up over time.
My notes saved in this browser