🧠
Better Approach Using Two Min-Heaps (Available and Running Machines)
💡 This approach uses two priority queues (min-heaps) to efficiently track which machines are free and which are busy, enabling quick assignment of jobs to earliest available machines.
Intuition
Maintain a min-heap of available machines sorted by machine index, and a min-heap of running machines sorted by their next free time. For each job, free up machines that have completed their jobs by the job's start time, then assign the job to the earliest available machine.
Algorithm
- Initialize a min-heap of available machines with all machine indices (0 to K-1).
- Initialize an empty min-heap for running machines, storing tuples of (free_time, machine_index).
- Sort jobs by their start time if not already sorted.
- For each job, pop machines from running heap whose free_time ≤ job start time and push them back to available heap.
- Pop the machine with smallest index from available heap and assign the job to it.
- Push the machine into running heap with updated free_time = job start + duration.
- Record the assigned machine index for each job.
💡 This approach cleverly uses two heaps to efficiently manage machine states and quickly find the earliest available machine.
import heapq
from typing import List
def assign_jobs_heap(K: int, jobs: List[List[int]]) -> List[int]:
n = len(jobs)
# Jobs are assumed sorted by start time; if not, sort with indices
indexed_jobs = sorted(enumerate(jobs), key=lambda x: x[1][0])
available = list(range(K))
heapq.heapify(available) # min-heap of machine indices
running = [] # min-heap of (free_time, machine_index)
result = [0] * n
for idx, (start, duration) in indexed_jobs:
# Free machines that have completed jobs by start time
while running and running[0][0] <= start:
free_time, machine = heapq.heappop(running)
heapq.heappush(available, machine)
# Assign job to earliest available machine
machine = heapq.heappop(available)
finish_time = start + duration
heapq.heappush(running, (finish_time, machine))
result[idx] = machine
return result
# Example usage
if __name__ == '__main__':
K = 2
jobs = [[0,5],[1,2],[5,3]]
print(assign_jobs_heap(K, jobs)) # Output: [0, 1, 0]
Line Notes
indexed_jobs = sorted(enumerate(jobs), key=lambda x: x[1][0])Sort jobs by start time while keeping original indices
available = list(range(K))Initialize all machines as available by their indices
heapq.heapify(available)Create a min-heap for available machines
while running and running[0][0] <= start:Free machines that have completed jobs before current job starts
machine = heapq.heappop(available)Assign job to machine with smallest index
finish_time = start + durationCalculate when machine will be free after current job
heapq.heappush(running, (finish_time, machine))Add machine to running heap with updated free time
result[idx] = machineRecord assigned machine for job
import java.util.*;
public class MeetingRoomsIII {
public static int[] assignJobsHeap(int K, int[][] jobs) {
int n = jobs.length;
Integer[] indices = new Integer[n];
for (int i = 0; i < n; i++) indices[i] = i;
Arrays.sort(indices, Comparator.comparingInt(i -> jobs[i][0]));
PriorityQueue<Integer> available = new PriorityQueue<>();
for (int i = 0; i < K; i++) available.offer(i);
PriorityQueue<int[]> running = new PriorityQueue<>(Comparator.comparingInt(a -> a[0]));
int[] result = new int[n];
for (int idx : indices) {
int start = jobs[idx][0];
int duration = jobs[idx][1];
while (!running.isEmpty() && running.peek()[0] <= start) {
available.offer(running.poll()[1]);
}
int machine = available.poll();
int finishTime = start + duration;
running.offer(new int[]{finishTime, machine});
result[idx] = machine;
}
return result;
}
public static void main(String[] args) {
int K = 2;
int[][] jobs = {{0,5},{1,2},{5,3}};
System.out.println(Arrays.toString(assignJobsHeap(K, jobs)));
}
}
Line Notes
Integer[] indices = new Integer[n];Create array to store original job indices
Arrays.sort(indices, Comparator.comparingInt(i -> jobs[i][0]));Sort jobs by start time using indices
PriorityQueue<Integer> available = new PriorityQueue<>();Min-heap for available machines by index
while (!running.isEmpty() && running.peek()[0] <= start) {Free machines that finished before job start
int machine = available.poll();Assign job to earliest available machine
int finishTime = start + duration;Calculate machine's next free time
running.offer(new int[]{finishTime, machine});Add machine to running heap
result[idx] = machine;Record assigned machine
#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>
using namespace std;
vector<int> assignJobsHeap(int K, const vector<vector<int>>& jobs) {
int n = jobs.size();
vector<int> indices(n);
for (int i = 0; i < n; i++) indices[i] = i;
sort(indices.begin(), indices.end(), [&](int a, int b) {
return jobs[a][0] < jobs[b][0];
});
priority_queue<int, vector<int>, greater<int>> available;
for (int i = 0; i < K; i++) available.push(i);
// running stores pairs of (free_time, machine_index)
priority_queue<pair<int,int>, vector<pair<int,int>>, greater<pair<int,int>>> running;
vector<int> result(n);
for (int idx : indices) {
int start = jobs[idx][0];
int duration = jobs[idx][1];
while (!running.empty() && running.top().first <= start) {
available.push(running.top().second);
running.pop();
}
int machine = available.top();
available.pop();
int finishTime = start + duration;
running.emplace(finishTime, machine);
result[idx] = machine;
}
return result;
}
int main() {
int K = 2;
vector<vector<int>> jobs = {{0,5},{1,2},{5,3}};
vector<int> res = assignJobsHeap(K, jobs);
for (int m : res) cout << m << ' ';
cout << '\n';
return 0;
}
Line Notes
vector<int> indices(n);Store original job indices for sorting
sort(indices.begin(), indices.end(), [&](int a, int b) {Sort jobs by start time using indices
priority_queue<int, vector<int>, greater<int>> available;Min-heap for available machines by index
while (!running.empty() && running.top().first <= start) {Free machines that finished before job start
int machine = available.top();Assign job to earliest available machine
int finishTime = start + duration;Calculate machine's next free time
running.emplace(finishTime, machine);Add machine to running heap
result[idx] = machine;Record assigned machine
function assignJobsHeap(K, jobs) {
const n = jobs.length;
const indices = [...Array(n).keys()];
indices.sort((a, b) => jobs[a][0] - jobs[b][0]);
const available = [];
for (let i = 0; i < K; i++) available.push(i);
available.sort((a,b) => a - b);
const running = [];
function heapPush(arr, val, compare) {
arr.push(val);
let i = arr.length - 1;
while (i > 0) {
let p = Math.floor((i - 1) / 2);
if (compare(arr[p], arr[i]) <= 0) break;
[arr[p], arr[i]] = [arr[i], arr[p]];
i = p;
}
}
function heapPop(arr, compare) {
const top = arr[0];
const last = arr.pop();
if (arr.length > 0) {
arr[0] = last;
let i = 0;
while (true) {
let left = 2 * i + 1, right = 2 * i + 2, smallest = i;
if (left < arr.length && compare(arr[left], arr[smallest]) < 0) smallest = left;
if (right < arr.length && compare(arr[right], arr[smallest]) < 0) smallest = right;
if (smallest === i) break;
[arr[i], arr[smallest]] = [arr[smallest], arr[i]];
i = smallest;
}
}
return top;
}
// Min-heap compare functions
const compareAvailable = (a,b) => a - b;
const compareRunning = (a,b) => a[0] - b[0];
// Heapify available
available.sort((a,b) => a-b);
const result = new Array(n);
for (const idx of indices) {
const [start, duration] = jobs[idx];
// Free machines that finished before start
while (running.length > 0 && running[0][0] <= start) {
const [_, machine] = heapPop(running, compareRunning);
heapPush(available, machine, compareAvailable);
}
const machine = heapPop(available, compareAvailable);
const finishTime = start + duration;
heapPush(running, [finishTime, machine], compareRunning);
result[idx] = machine;
}
return result;
}
// Example usage
const K = 2;
const jobs = [[0,5],[1,2],[5,3]];
console.log(assignJobsHeap(K, jobs)); // Output: [0, 1, 0]
Line Notes
const indices = [...Array(n).keys()];Create array of job indices for sorting
indices.sort((a, b) => jobs[a][0] - jobs[b][0]);Sort jobs by start time using indices
const available = []; for (let i = 0; i < K; i++) available.push(i);Initialize available machines by index
while (running.length > 0 && running[0][0] <= start) {Free machines that finished before job start
const machine = heapPop(available, compareAvailable);Assign job to earliest available machine
const finishTime = start + duration;Calculate machine's next free time
heapPush(running, [finishTime, machine], compareRunning);Add machine to running heap
result[idx] = machine;Record assigned machine
TimeO(n log K)
SpaceO(K + n)
Each job causes at most one push and pop on heaps of size up to K, so O(n log K) time. Space stores heaps and result arrays.
💡 For n=100,000 and K=100,000, this reduces operations to about 1.5 million, which is feasible.
Interview Verdict: Accepted
This approach is efficient and practical for large inputs, demonstrating mastery of heap data structures.