Builds on Top-K Heap, which you haven’t finished yet.
Event Simulation with a Ready Heap (Single-Threaded CPU, Task Scheduling)
You get n tasks numbered 0 to n - 1 as a list tasks, where tasks[i] = [enqueueTime_i, processingTime_i]: task i can start from time enqueueTime_i on, and it needs processingTime_i units of time.
One CPU runs the tasks, one at a time, and each task runs to the end without a break. Whenever the CPU is free, it looks at the tasks that have already arrived and have not run yet, and picks the one with the shortest processing time; between equal times, the smaller index wins. If no task is waiting, the CPU stays idle until one arrives. A new task can start at the very moment the previous one ends.
Return the task indices in the order the CPU runs them.
Ready Heap
items with arrival times ➔ a heap of the arrived items, ordered by the rule that picks the next one ➔ pop the best; if the heap is empty, set the clock to the next arrival| 1 | import heapq |
| 2 | |
| 3 | def get_order(tasks: list[list[int]]) -> list[int]: |
| 4 | """Single-Threaded CPU: run the shortest arrived task next, jumping the clock when idle. |
| 5 | Time: O(N log N) | Space: O(N) |
| 6 | """ |
| 7 | order = sorted(range(len(tasks)), key=lambda i: tasks[i][0]) # Task indices by arrival time |
| 8 | ready: list[tuple[int, int]] = [] # Min-heap of (duration, index) of arrived tasks; the root runs next |
| 9 | result: list[int] = [] |
| 10 | time = 0 |
| 11 | ptr = 0 # Next task in arrival order not yet released |
| 12 | |
| 13 | # 1. Jump the Clock When the CPU Is Idle: |
| 14 | # ready holds nothing, so the next task has not arrived yet; jumping straight to its arrival avoids ticking time forward one unit at a time. |
| 15 | while len(result) < len(tasks): |
| 16 | if not ready and time < tasks[order[ptr]][0]: |
| 17 | time = tasks[order[ptr]][0] # CPU idle: jump the clock to the next arrival |
| 18 | |
| 19 | # 2. Release Every Task That Has Arrived: |
| 20 | # Several tasks can arrive at or before the same time, so release all of them before picking, not just the next one. |
| 21 | while ptr < len(order) and tasks[order[ptr]][0] <= time: |
| 22 | i = order[ptr] |
| 23 | heapq.heappush(ready, (tasks[i][1], i)) # Release every task that has arrived by now |
| 24 | ptr += 1 |
| 25 | |
| 26 | # 3. Run the Shortest Ready Task: |
| 27 | # ready holds exactly the tasks that have arrived and not run, so its root is the CPU's next pick by the shortest-duration, smaller-index rule. |
| 28 | duration, i = heapq.heappop(ready) # Shortest arrived task, smaller index on ties |
| 29 | time += duration # It runs to the end without stopping |
| 30 | result.append(i) |
| 31 | |
| 32 | return result |
ready holds exactly the tasks that have arrived by time and have not run yet, so ready[0] is the task the CPU starts next.Only the tasks that have already arrived compete for the CPU, so sort the task indices by arrival once (order) and walk them with a pointer ptr: before each choice, push every task whose arrival is at most time onto the min-heap ready as (duration, i), then pop the root, run it, and add its duration to time. time never goes back, so ptr only moves forward and each task is pushed and popped exactly once; ready always holds exactly the arrived tasks that have not run, and the tuple order puts the shortest one on top, with the smaller index winning ties, which is the CPU's rule. The trap is the idle CPU: when ready is empty the next task has not arrived yet, so jump time to its arrival, tasks[order[ptr]][0], before releasing. Without the jump heapq.heappop(ready) pops an empty heap, and moving time forward one unit at a time takes up to 10^9 steps.
See it run
Ready Heap, step by step
time 0ptr 0
The columns of the first two rows are positions in arrival order: the task index, then its arrival time. The ready row is the heap array of (duration, index) tuples, shown as duration,index. The last row is result.
ptrthe next task in arrival order that is not released yetjumpthe arrival time that time is set to while the CPU is idlepusheda task that has arrived and is now in readyranthe index of the task that just ran
Start: time = 0, ptr = 0 and ready is empty. order lists the task indices by arrival, and the arrival row shows each arrival time.
Gotcha 1 of 4
- The trap
Gotcha 1: Not jumping
timeforward when the CPU is idleMistakewhile len(result) < len(tasks):Fixwhile len(result) < len(tasks): if not ready and time < tasks[order[ptr]][0]: time = tasks[order[ptr]][0]What happens: When
readyis empty, the next task has not arrived yet. Without the jump, the release loop releases nothing.heapq.heappop(ready)then pops an empty list and raises an IndexError. Movingtimeforward by 1 per step would take up to 10^9 steps, because an arrival can be 10^9. Jumptimestraight totasks[order[ptr]][0].Example: Single-Threaded CPU (LC 1834): tasks = [[1, 2], [2, 4], [3, 2], [4, 1]]Mistake: raises IndexErrorFix: returns [0, 2, 3, 1]
- Every item is there from the start and only the best
kmatter, as in K Closest Points to Origin (LC 973): there is no clock, and nothing becomes available later. See Top-K Heap - You only need how many intervals overlap at the busiest moment, as in Meeting Rooms II (LC 253), not which item is served next or where it goes. See Sweep Line
- An item you already took can be dropped again when a later deadline is missed, as in Course Schedule III (LC 630); here a task that has started always runs to the end. See Greedy with Regret
Deep Dive
Why only the arrived tasks are in the heap
The CPU may start a task only if the task has arrived. So at any time, the legal choices are exactly the tasks with arrival at most time that have not run. The rule of Single-Threaded CPU (LC 1834) picks the shortest duration among them, and the smaller index on a tie.
ready is a min-heap of (duration, i) tuples. Tuple order is the rule itself: it compares the duration first and the index second. So ready[0] is the task the CPU starts next, with no extra comparison code.
Tasks that have not arrived stay out of ready. They are in order, sorted by arrival, and ptr marks the first one that is not released. Before each pop, the loop moves every task with arrival at most time from order into ready. This keeps the invariant: ready holds exactly the arrived tasks that have not run.
Example: for tasks = [[1, 2], [2, 4], [3, 2], [4, 1]], the CPU starts task 0 at time 1 and finishes at 3. Tasks 1 and 2 have arrived by then, and task 2 is shorter, so it runs next and ends at 5. Task 3 has arrived at 4, so now it competes with task 1, and the shorter task 3 runs. The last is task 1. The result is [0, 2, 3, 1], which is the LeetCode answer. Task 3 is the shortest task overall, but it cannot run before it arrives.
One trace: a jump, two arrivals at once, and a tie
The board replays get_order on tasks = [[4, 1], [12, 3], [1, 2], [20, 2], [2, 4], [12, 3], [3, 2]]. Sorted by arrival, order is [2, 4, 6, 0, 1, 5, 3]. The input is not sorted, so order differs from the plain index order, and ready stores i and not ptr.
At the start, time is 0 and ready is empty. The first arrival is 1. The jump step sets time to 1 at once. Without the jump, the release loop releases nothing, and heapq.heappop(ready) fails on an empty heap.
At time = 3, the release loop pushes two tasks in one pass: (4, 4) and (2, 6). The loop is a while for this reason. With an if, only one task would be released before the pop.
After task 4 ends at time = 10, ready is empty again. The next arrival is 12, so there is a second jump. Tasks 1 and 5 both arrive at 12 with duration 3. The tuples (3, 1) and (3, 5) tie on duration, so index 1 runs first. Then task 5 runs, and a third jump goes to 20 for task 3.
The result is [2, 6, 0, 4, 1, 5, 3]. A second program that scans all tasks at every step and picks the best arrived one gives the same result on 2000 random inputs.
The cost: sort once, then each task enters and leaves once
Sorting order costs O(N log N). After that, ptr only moves forward, because time never goes back. So each task is pushed once and popped once. Each push and pop on ready costs O(log N), so the loop costs O(N log N). The total is O(N log N) time and O(N) space.
The jump is what makes the loop count depend on N and not on the arrival values. Each pass of the outer while runs exactly one task. So the loop runs N times, even when an arrival is 10^9.
Example: one task [[1000000000, 1]] needs one jump and one pass. A clock that moves forward by 1 until a task arrives would take 10^9 steps for it.
On the board input, the loop makes 7 pushes, 7 pops and 3 jumps. The jumps are free of heap work. Each one is a single assignment to time.
The same shape with a different heap key
This card adds one idea to Keep the Best K: a clock decides what is in the heap. The heap key and the way the clock advances change from problem to problem. The release loop, the idle jump and the pop stay the same.
In Process Tasks Using Servers (LC 1882), the heap holds the free servers, keyed by weight and then index. A second heap holds busy servers, keyed by the time they become free. When no server is free, the clock jumps to the earliest free time.
In Meeting Rooms III (LC 2402), the free rooms are keyed by room number, and the busy rooms by the end time of their meeting. A meeting that finds no free room starts when the earliest room ends.
In IPO (LC 502), the clock is your capital. Projects whose required capital is at most your capital move into a max-heap keyed by profit. You take the best one, and your capital grows by its profit.
In Maximum Number of Events That Can Be Attended (LC 1353), the clock is the day. Events that have started go into a heap keyed by end day. Each day you attend the event with the earliest end day, after you drop events that have ended. In Minimum Interval to Include Each Query (LC 1851), sorted queries play the role of the clock, and the heap key is the interval size.
60-Second Pitch
"Only tasks that have arrived can run, so I keep a min-heap ready of the arrived tasks, keyed by (duration, index). That tuple order is exactly the CPU's rule. I sort the task indices by arrival once, and walk them with a pointer. Before each pop, I push every task whose arrival is at most time. Then I pop the root, add its duration to time, and record its index. If ready is empty, I jump time to the next arrival. Arrivals can be 10^9, so I must not tick the clock one unit at a time. The clock never goes back, so the pointer only moves forward, and each task is pushed once and popped once. The time is O(N log N) and the space is O(N)."
Common Questions
- Why does the heap key have the index as a second field?
The problem says the smaller index wins when two tasks have the same duration. A tuple
(duration, i)compares the duration first and the index second, so the heap applies that rule by itself. Withouti, the heap would hold only durations, and it could not tell which task a duration belongs to.- What are the time and space complexity of
get_order? O(N log N)time andO(N)space. SortingordercostsO(N log N). Each task is pushed once and popped once, and each heap operation costsO(log N). The idle jump is one assignment, and there are at mostNof them.- Why is a jump needed, and why not move the clock by 1?
When
readyis empty, nothing can run until the next arrival, so the answer does not depend on the time in between. Arrivals go up to 10^9, so a clock that moves by 1 can take up to 10^9 steps. The jump setstime = tasks[order[ptr]][0]and keeps the loop atNpasses.- What changes if a running task can be stopped when a shorter task arrives?
A task no longer runs to the end. The clock must also stop at every arrival, not only at the end of a task. At each stop, put the unfinished task back into
readywith its remaining duration. This is the shortest remaining time rule, and it needs more passes than one per task.- How is this different from a max-heap of the best items, as in Top K Frequent Elements?
There, every item is in the heap from the start and you keep the best
k. Here, an item enters the heap only when the clock reaches its arrival, and you pop one item each time the resource is free. The heap holds the arrived items, not the best items so far.
SWE Reasoning
Spot it
- Each item has a time at which it becomes available: an arrival time, a start day, a capital you must reach.
- Each time the resource is free, you serve the best item that is already available, by a fixed rule such as the shortest task or the lowest room number.
- Something moves a clock forward: the end of a task, the end of a meeting, the next day.
- Sorting the items by availability once is enough, because the clock never goes back.
- Items can arrive long after the resource became free, so the resource may sit idle until the next arrival.
The invariant, step by step
orderlists the task indices sorted by arrival.readyis empty,timeis 0 andptris 0.- If
readyis empty and the next task has not arrived, settimeto its arrival. Nothing can run before then. - Push
(tasks[i][1], i)for every task whose arrival is at mosttime, and moveptrforward after each one. - Now
readyholds exactly the tasks that have arrived and not run. Its root is the shortest of them, with the smaller index on a tie. - Pop the root, add its duration to
time, and append its index toresult. The task runs to the end without stopping. timeonly grows, so a released task never needs to leavereadyuntil it is popped. The loop ends whenresulthaslen(tasks)entries.
Traps
- No idle jump. When
readyis empty,heapq.heappop(ready)raises an IndexError. A clock that ticks by 1 would need up to 10^9 steps. <instead of<=in the release test. A task that arrives exactly attimeis available. After a jump,timeequals the next arrival, so nothing is released and the pop fails.ifinstead ofwhilein the release loop. Tasks that arrive together are released one per pass. The CPU can run a long task while a shorter task has already arrived.ptrpushed in place ofi.ptris a position inorder. The answer must list indices oftasks, and the tie goes to the smaller index oftasks.- Pushing all tasks at the start. Then the CPU can pick a task that has not arrived yet, and the result is wrong.
Practice
Builds on Top-K Heap, which you haven’t finished yet.
Event Simulation with a Ready Heap (Single-Threaded CPU, Task Scheduling)
You get n tasks numbered 0 to n - 1 as a list tasks, where tasks[i] = [enqueueTime_i, processingTime_i]: task i can start from time enqueueTime_i on, and it needs processingTime_i units of time.
One CPU runs the tasks, one at a time, and each task runs to the end without a break. Whenever the CPU is free, it looks at the tasks that have already arrived and have not run yet, and picks the one with the shortest processing time; between equal times, the smaller index wins. If no task is waiting, the CPU stays idle until one arrives. A new task can start at the very moment the previous one ends.
Return the task indices in the order the CPU runs them.
Ready Heap
items with arrival times ➔ a heap of the arrived items, ordered by the rule that picks the next one ➔ pop the best; if the heap is empty, set the clock to the next arrival| 1 | import heapq |
| 2 | |
| 3 | def get_order(tasks: list[list[int]]) -> list[int]: |
| 4 | """Single-Threaded CPU: run the shortest arrived task next, jumping the clock when idle. |
| 5 | Time: O(N log N) | Space: O(N) |
| 6 | """ |
| 7 | order = sorted(range(len(tasks)), key=lambda i: tasks[i][0]) # Task indices by arrival time |
| 8 | ready: list[tuple[int, int]] = [] # Min-heap of (duration, index) of arrived tasks; the root runs next |
| 9 | result: list[int] = [] |
| 10 | time = 0 |
| 11 | ptr = 0 # Next task in arrival order not yet released |
| 12 | |
| 13 | # 1. Jump the Clock When the CPU Is Idle: |
| 14 | # ready holds nothing, so the next task has not arrived yet; jumping straight to its arrival avoids ticking time forward one unit at a time. |
| 15 | while len(result) < len(tasks): |
| 16 | if not ready and time < tasks[order[ptr]][0]: |
| 17 | time = tasks[order[ptr]][0] # CPU idle: jump the clock to the next arrival |
| 18 | |
| 19 | # 2. Release Every Task That Has Arrived: |
| 20 | # Several tasks can arrive at or before the same time, so release all of them before picking, not just the next one. |
| 21 | while ptr < len(order) and tasks[order[ptr]][0] <= time: |
| 22 | i = order[ptr] |
| 23 | heapq.heappush(ready, (tasks[i][1], i)) # Release every task that has arrived by now |
| 24 | ptr += 1 |
| 25 | |
| 26 | # 3. Run the Shortest Ready Task: |
| 27 | # ready holds exactly the tasks that have arrived and not run, so its root is the CPU's next pick by the shortest-duration, smaller-index rule. |
| 28 | duration, i = heapq.heappop(ready) # Shortest arrived task, smaller index on ties |
| 29 | time += duration # It runs to the end without stopping |
| 30 | result.append(i) |
| 31 | |
| 32 | return result |
ready holds exactly the tasks that have arrived by time and have not run yet, so ready[0] is the task the CPU starts next.Only the tasks that have already arrived compete for the CPU, so sort the task indices by arrival once (order) and walk them with a pointer ptr: before each choice, push every task whose arrival is at most time onto the min-heap ready as (duration, i), then pop the root, run it, and add its duration to time. time never goes back, so ptr only moves forward and each task is pushed and popped exactly once; ready always holds exactly the arrived tasks that have not run, and the tuple order puts the shortest one on top, with the smaller index winning ties, which is the CPU's rule. The trap is the idle CPU: when ready is empty the next task has not arrived yet, so jump time to its arrival, tasks[order[ptr]][0], before releasing. Without the jump heapq.heappop(ready) pops an empty heap, and moving time forward one unit at a time takes up to 10^9 steps.
See it run
Ready Heap, step by step
time 0ptr 0
The columns of the first two rows are positions in arrival order: the task index, then its arrival time. The ready row is the heap array of (duration, index) tuples, shown as duration,index. The last row is result.
ptrthe next task in arrival order that is not released yetjumpthe arrival time that time is set to while the CPU is idlepusheda task that has arrived and is now in readyranthe index of the task that just ran
Start: time = 0, ptr = 0 and ready is empty. order lists the task indices by arrival, and the arrival row shows each arrival time.
Gotcha 1 of 4
- The trap
Gotcha 1: Not jumping
timeforward when the CPU is idleMistakewhile len(result) < len(tasks):Fixwhile len(result) < len(tasks): if not ready and time < tasks[order[ptr]][0]: time = tasks[order[ptr]][0]What happens: When
readyis empty, the next task has not arrived yet. Without the jump, the release loop releases nothing.heapq.heappop(ready)then pops an empty list and raises an IndexError. Movingtimeforward by 1 per step would take up to 10^9 steps, because an arrival can be 10^9. Jumptimestraight totasks[order[ptr]][0].Example: Single-Threaded CPU (LC 1834): tasks = [[1, 2], [2, 4], [3, 2], [4, 1]]Mistake: raises IndexErrorFix: returns [0, 2, 3, 1]
- Every item is there from the start and only the best
kmatter, as in K Closest Points to Origin (LC 973): there is no clock, and nothing becomes available later. See Top-K Heap - You only need how many intervals overlap at the busiest moment, as in Meeting Rooms II (LC 253), not which item is served next or where it goes. See Sweep Line
- An item you already took can be dropped again when a later deadline is missed, as in Course Schedule III (LC 630); here a task that has started always runs to the end. See Greedy with Regret
Deep Dive
Why only the arrived tasks are in the heap
The CPU may start a task only if the task has arrived. So at any time, the legal choices are exactly the tasks with arrival at most time that have not run. The rule of Single-Threaded CPU (LC 1834) picks the shortest duration among them, and the smaller index on a tie.
ready is a min-heap of (duration, i) tuples. Tuple order is the rule itself: it compares the duration first and the index second. So ready[0] is the task the CPU starts next, with no extra comparison code.
Tasks that have not arrived stay out of ready. They are in order, sorted by arrival, and ptr marks the first one that is not released. Before each pop, the loop moves every task with arrival at most time from order into ready. This keeps the invariant: ready holds exactly the arrived tasks that have not run.
Example: for tasks = [[1, 2], [2, 4], [3, 2], [4, 1]], the CPU starts task 0 at time 1 and finishes at 3. Tasks 1 and 2 have arrived by then, and task 2 is shorter, so it runs next and ends at 5. Task 3 has arrived at 4, so now it competes with task 1, and the shorter task 3 runs. The last is task 1. The result is [0, 2, 3, 1], which is the LeetCode answer. Task 3 is the shortest task overall, but it cannot run before it arrives.
One trace: a jump, two arrivals at once, and a tie
The board replays get_order on tasks = [[4, 1], [12, 3], [1, 2], [20, 2], [2, 4], [12, 3], [3, 2]]. Sorted by arrival, order is [2, 4, 6, 0, 1, 5, 3]. The input is not sorted, so order differs from the plain index order, and ready stores i and not ptr.
At the start, time is 0 and ready is empty. The first arrival is 1. The jump step sets time to 1 at once. Without the jump, the release loop releases nothing, and heapq.heappop(ready) fails on an empty heap.
At time = 3, the release loop pushes two tasks in one pass: (4, 4) and (2, 6). The loop is a while for this reason. With an if, only one task would be released before the pop.
After task 4 ends at time = 10, ready is empty again. The next arrival is 12, so there is a second jump. Tasks 1 and 5 both arrive at 12 with duration 3. The tuples (3, 1) and (3, 5) tie on duration, so index 1 runs first. Then task 5 runs, and a third jump goes to 20 for task 3.
The result is [2, 6, 0, 4, 1, 5, 3]. A second program that scans all tasks at every step and picks the best arrived one gives the same result on 2000 random inputs.
The cost: sort once, then each task enters and leaves once
Sorting order costs O(N log N). After that, ptr only moves forward, because time never goes back. So each task is pushed once and popped once. Each push and pop on ready costs O(log N), so the loop costs O(N log N). The total is O(N log N) time and O(N) space.
The jump is what makes the loop count depend on N and not on the arrival values. Each pass of the outer while runs exactly one task. So the loop runs N times, even when an arrival is 10^9.
Example: one task [[1000000000, 1]] needs one jump and one pass. A clock that moves forward by 1 until a task arrives would take 10^9 steps for it.
On the board input, the loop makes 7 pushes, 7 pops and 3 jumps. The jumps are free of heap work. Each one is a single assignment to time.
The same shape with a different heap key
This card adds one idea to Keep the Best K: a clock decides what is in the heap. The heap key and the way the clock advances change from problem to problem. The release loop, the idle jump and the pop stay the same.
In Process Tasks Using Servers (LC 1882), the heap holds the free servers, keyed by weight and then index. A second heap holds busy servers, keyed by the time they become free. When no server is free, the clock jumps to the earliest free time.
In Meeting Rooms III (LC 2402), the free rooms are keyed by room number, and the busy rooms by the end time of their meeting. A meeting that finds no free room starts when the earliest room ends.
In IPO (LC 502), the clock is your capital. Projects whose required capital is at most your capital move into a max-heap keyed by profit. You take the best one, and your capital grows by its profit.
In Maximum Number of Events That Can Be Attended (LC 1353), the clock is the day. Events that have started go into a heap keyed by end day. Each day you attend the event with the earliest end day, after you drop events that have ended. In Minimum Interval to Include Each Query (LC 1851), sorted queries play the role of the clock, and the heap key is the interval size.
60-Second Pitch
"Only tasks that have arrived can run, so I keep a min-heap ready of the arrived tasks, keyed by (duration, index). That tuple order is exactly the CPU's rule. I sort the task indices by arrival once, and walk them with a pointer. Before each pop, I push every task whose arrival is at most time. Then I pop the root, add its duration to time, and record its index. If ready is empty, I jump time to the next arrival. Arrivals can be 10^9, so I must not tick the clock one unit at a time. The clock never goes back, so the pointer only moves forward, and each task is pushed once and popped once. The time is O(N log N) and the space is O(N)."
Common Questions
- Why does the heap key have the index as a second field?
The problem says the smaller index wins when two tasks have the same duration. A tuple
(duration, i)compares the duration first and the index second, so the heap applies that rule by itself. Withouti, the heap would hold only durations, and it could not tell which task a duration belongs to.- What are the time and space complexity of
get_order? O(N log N)time andO(N)space. SortingordercostsO(N log N). Each task is pushed once and popped once, and each heap operation costsO(log N). The idle jump is one assignment, and there are at mostNof them.- Why is a jump needed, and why not move the clock by 1?
When
readyis empty, nothing can run until the next arrival, so the answer does not depend on the time in between. Arrivals go up to 10^9, so a clock that moves by 1 can take up to 10^9 steps. The jump setstime = tasks[order[ptr]][0]and keeps the loop atNpasses.- What changes if a running task can be stopped when a shorter task arrives?
A task no longer runs to the end. The clock must also stop at every arrival, not only at the end of a task. At each stop, put the unfinished task back into
readywith its remaining duration. This is the shortest remaining time rule, and it needs more passes than one per task.- How is this different from a max-heap of the best items, as in Top K Frequent Elements?
There, every item is in the heap from the start and you keep the best
k. Here, an item enters the heap only when the clock reaches its arrival, and you pop one item each time the resource is free. The heap holds the arrived items, not the best items so far.
SWE Reasoning
Spot it
- Each item has a time at which it becomes available: an arrival time, a start day, a capital you must reach.
- Each time the resource is free, you serve the best item that is already available, by a fixed rule such as the shortest task or the lowest room number.
- Something moves a clock forward: the end of a task, the end of a meeting, the next day.
- Sorting the items by availability once is enough, because the clock never goes back.
- Items can arrive long after the resource became free, so the resource may sit idle until the next arrival.
The invariant, step by step
orderlists the task indices sorted by arrival.readyis empty,timeis 0 andptris 0.- If
readyis empty and the next task has not arrived, settimeto its arrival. Nothing can run before then. - Push
(tasks[i][1], i)for every task whose arrival is at mosttime, and moveptrforward after each one. - Now
readyholds exactly the tasks that have arrived and not run. Its root is the shortest of them, with the smaller index on a tie. - Pop the root, add its duration to
time, and append its index toresult. The task runs to the end without stopping. timeonly grows, so a released task never needs to leavereadyuntil it is popped. The loop ends whenresulthaslen(tasks)entries.
Traps
- No idle jump. When
readyis empty,heapq.heappop(ready)raises an IndexError. A clock that ticks by 1 would need up to 10^9 steps. <instead of<=in the release test. A task that arrives exactly attimeis available. After a jump,timeequals the next arrival, so nothing is released and the pop fails.ifinstead ofwhilein the release loop. Tasks that arrive together are released one per pass. The CPU can run a long task while a shorter task has already arrived.ptrpushed in place ofi.ptris a position inorder. The answer must list indices oftasks, and the tie goes to the smaller index oftasks.- Pushing all tasks at the start. Then the CPU can pick a task that has not arrived yet, and the result is wrong.