2Brute2Best
🔥0

Process Tasks Using Servers

Medium·LeetCode #1882Basic Anchor
LeetCode
30-Second Mental ModelTwo Heaps — Two pools (available / locked)
Complexity:O(M · N)→O((N + M) · log N)
Key Insight

Keep two heaps. The free pool holds servers that can take a task now, lightest on top. The busy pool holds servers that are still working, earliest finish on top. A busy server only needs to be checked when the clock reaches its finish time, so the busy pool can be a heap keyed by that time.

Core Validity Invariant

A server is in the free pool exactly when its freeTime ≤ t. The busy pool is checked only at finish times, so when we pick a server, the free pool is already correct for second t.

01

Problem

You have n servers and m tasks. Server i has a weight servers[i]. Task j takes tasks[j] seconds to run.

Task j joins a queue at second j, so task 0 joins at second 0, task 1 at second 1, and so on. The queue is served in order. Whenever the task at the front of the queue is waiting and a server is free, give the task to the free server with the smallest weight (ties go to the smaller index). If no server is free, the task waits until one is.

A server that starts a task at second t is busy until second t + tasks[j], and it is free again at that second. Several servers can become free in the same second.

Return ans, where ans[j] is the index of the server that runs task j.

Input

servers, the weight of each server, and tasks, the seconds each task takes.

Output

An array ans of length m: the server index for each task.

Example 1
Input: servers = [3,3,2], tasks = [1,2,3,2,1,2]
Output: [2,2,0,2,1,2]

At second 0, task 0 goes to server 2 (weight 2, the lightest) and runs until second 1. At second 1, server 2 is free again and takes task 1 until second 3. At second 2, servers 0 and 1 are free and have the same weight, so the smaller index, server 0, takes task 2. The rest follows the same rule.

Example 2
Input: servers = [5,1,4,3,2], tasks = [2,1,2,4,5,2,1]
Output: [1,4,1,4,1,3,2]

At second 0, task 0 goes to server 1 (weight 1) until second 2. At second 1, task 1 goes to server 4 (weight 2) until second 2. At second 2 both servers are free again, and task 2 takes server 1, the lighter one.

Constraints
  • • servers.length == n
  • • tasks.length == m
  • • 1 ≤ n, m ≤ 2 × 10^5
  • • 1 ≤ servers[i], tasks[j] ≤ 2 × 10^5
02

First, think yourself

Before opening any solution, try to answer these on paper. Use hints only when stuck — each hint is counted.

  1. 1.

    Tasks wait in a queue. For one task, what decides which server it gets?

  2. 2.

    If every server is busy, does the clock need to move one second at a time, or can it skip ahead?

  3. 3.

    A server starts a task at second t and the task takes d seconds. At which second is that server free again?

  4. 4.

    Can the clock ever go back to an earlier second when the next task is handled?

  5. 5.

    If two servers finish in the same second, do I need to release both before I choose?

03

Brute force approach

05

Optimized approach

06

Why does this algorithm work?

07

Dry run

08

All approaches compared

ApproachIdeaTimeSpace
Brute ForceFor each task, check all servers; if none is free, jump the clock to the earliest finishO(M · N)O(N)
OptimalFree servers in a min-heap by (weight, index); busy servers in a min-heap by finish timeO((N + M) · log N)O(N)
09

Why does this belong to this pattern?

Clues in the problem statement
  • “tasks join a queue, one per second”Tasks are served in order and time only moves forward, so the clock is a single pointer `t`.
  • “smallest weight, then smallest index”The free servers form a min-heap keyed by (weight, index).
  • “busy until t + tasks[j]”Busy servers form a second min-heap keyed by finish time, so the next server to come back is at the top.
items come in order and need a free resource+pick the best free resource (lightest server)+resources come back at known times (finish times)→Two Heaps — Two pools (available / locked)

The two pools are free (usable now) and busy (locked until its finish time). A server moves from busy back to free when the clock reaches its finish time, and it moves the other way when it takes a task, so items go back and forth. The neighbours in this sub-pattern differ: IPO moves projects one way only (locked to available), and Total Cost keeps two pools at the two ends of one list.

10

Don't make this mistake

Heap direction: the heaviest server comes out first
✗ priority_queue<pair<int, int>> freeHeap; // max-heap by default
✓ priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> freeHeap;

C++ priority_queue is a max-heap by default. On the dry run the free pool holds (4, server 0) and (1, server 1). The max-heap pops server 0 first, so task 0 goes to the heavy server (weight 4) instead of the light one (weight 1). The busy pool needs the same fix: keyed by finish time, earliest first.

Pattern confusion: starting the task at its arrival second
✗ busy.push({j + tasks[j], w, s});
✓ busy.push({t + tasks[j], w, s});

On the dry run, task 2 arrives at second 2, but both servers are busy until second 3. It really starts at 3 and runs until 4, not until 2 + 1 = 3. With the wrong line, server 1 looks free again at second 3 and takes task 3, so the answer becomes [1, 0, 1, 1, 1, 1] instead of [1, 0, 1, 0, 1, 1]. Stepping the clock one second at a time is also correct but slow: one server with 2 × 10^5 tasks of 2 × 10^5 seconds reaches second 4 × 10^10. Jump instead.

Releasing one server per task instead of all of them
✗ if (!busy.empty() && get<0>(busy.top()) <= t) { /* release one */ }
✓ while (!busy.empty() && get<0>(busy.top()) <= t) { /* release one */ }

On the dry run, at second 4 servers 0 and 1 are both due, but the if releases only one (server 1). At second 5 task 5 releases server 0 (weight 4), and server 1 is still busy because its finish time is also 5 and only one server is released per task. Task 5 takes the heavy server, so the last answer is 0 instead of 1.

Overflow in the clock
✗ int t = 0; // finish times can pass 2^31
✓ long long t = 0; // and long long for freeTime

One server with 2 × 10^5 tasks of 2 × 10^5 seconds finishes at second 4 × 10^10, which overflows a 32-bit int.

11

Want to implement this?

Write it yourself on LeetCode, then come back and compare your code with the solutions above.

Open coding workspace
How did it go?
Beyond The LeetCode Problem

How this concept connects to System Design & C++ Systems

Bridge DSA with production engineering

Top engineering interviews test whether you understand how algorithms like Two Heaps translate into production architectures and low-level memory mechanics.