Resource allocation
Greedy resource allocation: job sequencing with deadlines, fractional knapsack, minimum platforms, gas station and assigning cookies.
Learning goals
- Schedule unit jobs with deadlines for maximum profit
- Solve the fractional knapsack by value density
- Compute minimum platforms with sorted arrivals and departures
- Find the unique start of the gas-station circuit in one pass
- Match supply and demand greedily with two pointers
Prerequisites
Scarce resources (time slots, capacity, fuel, cookies) and many demands: greedy gives the best allocation when the demands can be ranked consistently.
Job sequencing with deadlines
Each job takes one unit of time, has a deadline and a profit, and only one job runs per slot. Maximise total profit. Sort jobs by **profit descending** and place each in the **latest free slot at or before its deadline**; skip it if no such slot is free. Placing late keeps earlier slots open for jobs with tighter deadlines.
The “Classic” preset (deadline:profit pairs 2:100, 1:19, 2:27, 1:25, 3:15) earns 142 from 3 jobs: profit 100 takes slot 2, 27 takes slot 1, the jobs with deadline 1 find no slot, and 15 takes slot 3. “All want slot 1” shows that only the most profitable job fits.
The slot search here is a simple scan, so the worst case is O(n · maxDeadline). A union-find structure over slots reduces the search to nearly constant time per job; that structure is covered later in the course.
1// jobs[i] = {deadline, profit}; returns the maximum total profit2static int jobSequencing(int[][] jobs) {3int[][] a = jobs.clone();4Arrays.sort(a, (x, y) -> Integer.compare(y[1], x[1])); // most profitable first5int maxDeadline = 0;6for (int[] j : a) maxDeadline = Math.max(maxDeadline, j[0]);7boolean[] taken = new boolean[maxDeadline + 1]; // slots 1..maxDeadline8int total = 0;9for (int[] j : a) {10for (int slot = j[0]; slot >= 1; slot--) { // latest free slot not after the deadline11if (!taken[slot]) { taken[slot] = true; total += j[1]; break; }12}13}14return total;15}
Best
O(n log n)
Average
O(n log n + n·D)
Worst
O(n log n + n·D)
Space
O(D) for the slot array, D = largest deadline
Job sequencing with deadlines
Interactive · edit the inputjobs by profit
time slots
Every job takes one slot and must finish by its deadline. Take jobs in order of profit and give each the latest free slot on or before its deadline, which leaves earlier slots free for jobs with tighter deadlines.
1static int maxProfit(int[][] jobs) { // jobs[i] = {deadline, profit}, each takes one time slot2Arrays.sort(jobs, (a, b) -> Integer.compare(b[1], a[1])); // most profitable first3int maxDeadline = Arrays.stream(jobs).mapToInt(j -> j[0]).max().getAsInt();4boolean[] taken = new boolean[maxDeadline + 1]; int profit = 0;5for (int[] j : jobs) {6for (int slot = j[0]; slot >= 1; slot--) { // latest free slot on or before the deadline7if (!taken[slot]) { taken[slot] = true; profit += j[1]; break; }8}9}10return profit;11}
Live state
jobs
5
profit
0
Execution trace (dry run table)Show 10 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L2 | jobs=5 profit=0 | Every job takes one slot and must finish by its deadline. Take jobs in order of profit and give each the latest free slot on or before its deadline, which leaves earlier slots free for jobs with tighter deadlines. |
| 2 | L7 | job=J1 slot=2 profit=100 | Schedule J1 in slot 2: profit 100. |
| 3 | L6 | job=J3 slot=2 | Slot 2 is taken: try the one before it. |
| 4 | L7 | job=J3 slot=1 profit=127 | Schedule J3 in slot 1: profit 127. |
| 5 | L6 | job=J4 slot=1 | Slot 1 is taken: try the one before it. |
| 6 | L6 | job=J4 profit=127 | No free slot on or before 1: J4 is dropped. |
| 7 | L6 | job=J2 slot=1 | Slot 1 is taken: try the one before it. |
| 8 | L6 | job=J2 profit=127 | No free slot on or before 1: J2 is dropped. |
| 9 | L7 | job=J5 slot=3 profit=142 | Schedule J5 in slot 3: profit 142. |
| 10 | L10 | return=142 | Total profit 142 from 3 jobs. |
Fractional knapsack
Items have weights and values and the thief may take **any fraction** of an item. Sort by value per unit weight, take whole items from the best down, and take a fraction of the first item that does not fit. The “Classic (capacity 50)” preset (weight:value pairs 10:60, 20:100, 30:120) takes the first two whole and two thirds of the third: 60 + 100 + 80 = 240.
This succeeds because the exchange argument works: if the bag holds some of a worse-ratio item while a better-ratio item is not fully taken, swapping a little weight from the worse to the better item raises the value. With whole items only (0/1 knapsack) the swap may be impossible, and greedy fails; that problem is solved by dynamic programming later in this part.
1// items[i] = {weight, value}2static double fractionalKnapsack(int[][] items, int capacity) {3int[][] a = items.clone();4Arrays.sort(a, (x, y) -> Double.compare((double) y[1] / y[0], (double) x[1] / x[0]));5double value = 0;6int room = capacity;7for (int[] it : a) {8if (room == 0) break;9int take = Math.min(room, it[0]); // whole item, or whatever room is left10value += (double) it[1] * take / it[0];11room -= take;12}13return value;14}
Best
O(n log n)
Average
O(n log n)
Worst
O(n log n)
Space
O(n) for the sorted copy
Fractional knapsack by value per weight
Interactive · edit the inputitems by value/weight
taken (weight)
Capacity 50. Items may be split, so always take the item with the best value per unit of weight first.
1static double maxValue(int[] w, int[] v, int capacity) {2Integer[] idx = new Integer[w.length]; for (int i = 0; i < idx.length; i++) idx[i] = i;3Arrays.sort(idx, (a, b) -> Double.compare((double) v[b] / w[b], (double) v[a] / w[a])); // best value per weight first4double value = 0; int room = capacity;5for (int i : idx) {6if (room == 0) break;7int take = Math.min(room, w[i]); // the whole item, or the fraction that fits8value += (double) v[i] * take / w[i];9room -= take;10}11return value;12}
Live state
capacity
50
room
50
value
0
Execution trace (dry run table)Show 5 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L3 | capacity=50 room=50 value=0 | Capacity 50. Items may be split, so always take the item with the best value per unit of weight first. |
| 2 | L6 | item=#1 taken=10 room=40 value=60 | Take all of item #1 (10 weight, value 60). Room left: 40. |
| 3 | L6 | item=#2 taken=20 room=20 value=160 | Take all of item #2 (20 weight, value 100). Room left: 20. |
| 4 | L6 | item=#3 taken=20 room=0 value=240 | Only 20 of item #3's 30 weight fits: take that fraction for value 80.00. The knapsack is full. |
| 5 | L11 | return=240 room=0 | Maximum value 240.00. Greedy is optimal here because fractions of any item are allowed. |
Minimum platforms
Trains have arrival and departure times; how many platforms are needed so no train waits? Sort arrivals and departures **separately** and sweep with two pointers: if the next arrival is before the next departure, a platform is needed (count++), otherwise a train has left (count--). The maximum count is the answer. This is the event sweep, developed fully in Chapter 22, applied to trains.
The tie rule matters. In the half-open model a train leaving at time 3 frees the platform for one arriving at 3, so the “Back-to-back: half-open needs 1” preset needs 1 platform; if the station needs a gap between trains (closed intervals) the same data needs 2. The animation lets you switch the option and watch the answer change.
1static int minPlatforms(int[] arrival, int[] departure) {2int[] arr = arrival.clone(), dep = departure.clone();3Arrays.sort(arr);4Arrays.sort(dep);5int platforms = 0, best = 0, j = 0;6for (int i = 0; i < arr.length; i++) {7while (dep[j] <= arr[i]) { j++; platforms--; } // half-open: leaving at t frees the platform for an arrival at t8platforms++;9best = Math.max(best, platforms);10}11return best;12}
Best
O(n log n)
Average
O(n log n)
Worst
O(n log n)
Space
O(n) for the sorted copies
Start/end events: active count and maximum concurrency
Interactive · edit the inputintervals
events in sweep order
active count after each event
Turn every interval into two events, +1 at its start and −1 at its end, sort them, and sweep once keeping a running count. At equal coordinates ends come first, because [s, e) does not contain e: back-to-back intervals do not overlap.
1static int maxConcurrent(int[][] iv, boolean closed) {2List<int[]> events = new ArrayList<>(); // {coordinate, +1 start / -1 end}3for (int[] x : iv) { events.add(new int[]{x[0], +1}); events.add(new int[]{x[1], -1}); }4events.sort((a, b) -> a[0] != b[0] ? Integer.compare(a[0], b[0])5: closed ? Integer.compare(b[1], a[1]) : Integer.compare(a[1], b[1])); // tie: closed -> start first; half-open -> end first6int active = 0, best = 0;7for (int[] e : events) {8active += e[1]; // a start opens one, an end closes one9best = Math.max(best, active);10}11return best;12}
Live state
intervals
4
events
8
Execution trace (dry run table)Show 10 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L2 | intervals=4 events=8 | Turn every interval into two events, +1 at its start and −1 at its end, sort them, and sweep once keeping a running count. At equal coordinates ends come first, because [s, e) does not contain e: back-to-back intervals do not overlap. |
| 2 | L8 | x=0 event=start active=1 best=1 | At 0: an interval starts, so 1 is active. New maximum: 1. |
| 3 | L8 | x=5 event=start active=2 best=2 | At 5: an interval starts, so 2 are active. New maximum: 2. |
| 4 | L8 | x=10 event=end active=1 best=2 | At 10: an interval ends, so 1 is active. |
| 5 | L8 | x=10 event=start active=2 best=2 | At 10: an interval starts, so 2 are active. |
| 6 | L8 | x=15 event=end active=1 best=2 | At 15: an interval ends, so 1 is active. |
| 7 | L8 | x=15 event=start active=2 best=2 | At 15: an interval starts, so 2 are active. |
| 8 | L8 | x=20 event=end active=1 best=2 | At 20: an interval ends, so 1 is active. |
| 9 | L8 | x=30 event=end active=0 best=2 | At 30: an interval ends, so 0 are active. |
| 10 | L11 | return=2 | Maximum concurrency 2: that many rooms (or platforms, or CPUs) are needed. Cost O(n log n) for the sort, O(n) for the sweep. |
Gas station
Stations sit on a circle; station i has gas[i] fuel and the leg to the next station costs cost[i]. Find the station to start from to complete the circuit, or report that none exists. Two observations give an O(n) algorithm. If total gas is less than total cost, no start works. Otherwise, a start exists, and it is found by scanning: keep the running tank; whenever it goes negative at station i, no station between the current start and i can be the answer, so restart from i + 1.
The first preset (gas 1 2 3 4 5, cost 3 4 5 1 2) restarts twice and ends with start station 3. “Impossible” has total net fuel -1, so the algorithm stops at once.
1static int canCompleteCircuit(int[] gas, int[] cost) {2int total = 0, tank = 0, start = 0;3for (int i = 0; i < gas.length; i++) {4int net = gas[i] - cost[i];5total += net;6tank += net;7if (tank < 0) { start = i + 1; tank = 0; } // stations start..i cannot be the start8}9return total >= 0 ? start : -1;10}
Best
O(n)
Average
O(n)
Worst
O(n)
Space
O(1)
Gas station: the starting stop of a circular trip
Interactive · edit the inputgas
cost to next
tank after stop
Drive around once, tracking the tank from the current candidate start. If the tank ever goes negative, no station between the start and here can be a valid start either, so the next stop becomes the new candidate.
1static int startStation(int[] gas, int[] cost) {2int total = 0, tank = 0, start = 0;3for (int i = 0; i < gas.length; i++) {4int gain = gas[i] - cost[i]; // net fuel at this stop5total += gain; tank += gain;6if (tank < 0) { start = i + 1; tank = 0; } // cannot reach i + 1 from the current start: restart after i7}8return total >= 0 ? start : -1; // enough fuel overall means the last candidate works9}
Live state
total
0
tank
0
start
0
Execution trace (dry run table)Show 10 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L2 | total=0 tank=0 start=0 | Drive around once, tracking the tank from the current candidate start. If the tank ever goes negative, no station between the start and here can be a valid start either, so the next stop becomes the new candidate. |
| 2 | L5 | stop=0 gain=-2 tank=-2 total=-2 start=0 | Stop 0: +1 gas, -3 to reach the next stop: net -2. Tank -2, total -2. |
| 3 | L6 | stop=0 start=1 tank=0 | The tank is negative: the trip from the old start cannot pass stop 0. Restart at stop 1 with an empty tank. |
| 4 | L5 | stop=1 gain=-2 tank=-2 total=-4 start=1 | Stop 1: +2 gas, -4 to reach the next stop: net -2. Tank -2, total -4. |
| 5 | L6 | stop=1 start=2 tank=0 | The tank is negative: the trip from the old start cannot pass stop 1. Restart at stop 2 with an empty tank. |
| 6 | L5 | stop=2 gain=-2 tank=-2 total=-6 start=2 | Stop 2: +3 gas, -5 to reach the next stop: net -2. Tank -2, total -6. |
| 7 | L6 | stop=2 start=3 tank=0 | The tank is negative: the trip from the old start cannot pass stop 2. Restart at stop 3 with an empty tank. |
| 8 | L5 | stop=3 gain=3 tank=3 total=-3 start=3 | Stop 3: +4 gas, -1 to reach the next stop: net +3. Tank 3, total -3. |
| 9 | L5 | stop=4 gain=3 tank=6 total=0 start=3 | Stop 4: +5 gas, -2 to reach the next stop: net +3. Tank 6, total 0. |
| 10 | L8 | total=0 return=3 | Total net fuel 0 is not negative, so starting at stop 3 completes the circuit. |
Practice ladder
Now try it yourself
Easy
- Assign Cookies
- Lemonade Change
Medium
- Gas Station
- Maximum Units on a Truck
- Meeting Rooms II
Advanced
- Maximum Profit in Job Scheduling
- IPO
Key takeaways
- Job sequencing: sort by profit, place each job in the latest free slot not after its deadline.
- Fractional knapsack: sort by value per weight; fractions make greedy optimal, whole items do not.
- Minimum platforms is the maximum overlap, computed by sweeping sorted arrivals and departures.
- Gas station: if total gas covers total cost, restart the start point whenever the running tank goes negative.
- Match the smallest sufficient supply to the smallest demand with two pointers.