Greedy Algorithms · Greedy Fundamentals

Activity Selection Greedy

Sorting intervals by end time and always taking the one that finishes soonest among the remaining candidates - the move that leaves the most room for everything after it.

  • 5 min read
  • 8 practice questions
  • 2 graded problems
On this page
  1. Description
  2. The approach
  3. A second flavor: the fewest points that hit every interval
  4. Where you'll see it
  5. Which tool for which job?
  6. Common mistakes
  7. Check yourself
  8. Key takeaways
  9. Quick revise
  10. Practice

Description

You have one room and many meeting requests with start and end times, and you want to hold as many meetings as possible. Checking every subset is exponential. The obvious greedy orders, earliest start or shortest meeting first, both fail. Sorting by end time and taking every meeting that still fits is provably right. The same sort also finds the fewest points that hit every interval, and the fewest intervals to remove so the rest don't overlap.

We'll go from simple to more technical:

  1. The pattern: sort by end, keep every meeting that starts after the last kept one ends.
  2. A second flavor: the fewest arrows (points) that hit every interval.
  3. Where interval scheduling shows up (rooms, CPUs, ad slots, maintenance windows).
  4. What breaks it: the wrong sort key, the boundary rule, comparator overflow, weights.

The approach

Java

int maxMeetings(int[] start, int[] end) {
    Integer[] order = new Integer[start.length];
    for (int i = 0; i < order.length; i++) order[i] = i;
    Arrays.sort(order, (a, b) -> Integer.compare(end[a], end[b]));   // earliest end first
    int count = 0;
    long lastEnd = Long.MIN_VALUE;
    for (int i : order) {
        if (start[i] > lastEnd) {          // strictly after: touching meetings conflict in this problem
            count++;
            lastEnd = end[i];
        }
    }
    return count;
}

Python

def max_meetings(start: list[int], end: list[int]) -> int:
    count = 0
    last_end = float("-inf")
    for e, s in sorted(zip(end, start)):         # earliest end first
        if s > last_end:                         # strictly after: touching meetings conflict in this problem
            count += 1
            last_end = e
    return count

Taking the meeting that ends first leaves the room free as early as possible for everything else. Exchange argument: any optimal schedule's first meeting ends no earlier than the earliest-ending one, so swapping it for the earliest-ending one keeps the schedule valid and the same size.

The other orders have counterexamples. Earliest start takes [1, 10] from {[1, 10], [2, 3], [4, 5]} and holds 1 meeting instead of 2. Shortest first takes [4, 7] from {[1, 5], [4, 7], [6, 10]} and blocks both others. Whether a meeting may start exactly when another ends is a rule of the problem, not the algorithm: here it may not (>), so [1, 2], [2, 3], [3, 4] allows 2 meetings; with back-to-back allowed (>=) it would be 3.

A second flavor: the fewest points that hit every interval

Balloons span [start, end] on a line, and an arrow shot at x bursts every balloon with start <= x <= end. The fewest arrows uses the same sort:

Java

int findMinArrowShots(int[][] points) {
    Arrays.sort(points, (a, b) -> Integer.compare(a[1], b[1]));   // never a[1] - b[1]: it can overflow
    int arrows = 1;
    int pos = points[0][1];                // shoot at the earliest end
    for (int[] p : points) {
        if (p[0] > pos) {                  // starts after the arrow: needs a new one
            arrows++;
            pos = p[1];
        }
    }
    return arrows;
}

Python

def find_min_arrow_shots(points: list[list[int]]) -> int:
    points = sorted(points, key=lambda p: p[1])  # a key function: nothing to overflow
    arrows = 1
    pos = points[0][1]                           # shoot at the earliest end
    for start, end in points:
        if start > pos:                          # starts after the arrow: needs a new one
            arrows += 1
            pos = end
    return arrows

Shooting at the earliest end bursts every balloon that has already started, as late as possible. Balloons that merely touch the arrow's position are burst (closed intervals, so > starts a new arrow). With coordinates near ±2^31, the comparator a[1] - b[1] overflows: -2147483647 - 2147483647 wraps to 2, sorting the wrong balloon first. Integer.compare doesn't overflow. In Python you sort with a key function - sorted(points, key=lambda p: p[1]) - so there's no comparator to get wrong.

The minimum number of intervals to remove so the rest don't overlap is n minus the maximum number you can keep - the same greedy again.

Where you'll see it

Booking a single shared resource - a meeting room, a test device, one build agent - to serve the most requests; picking the maximum set of non-conflicting calendar events; and placing the fewest monitoring checkpoints that cover every time window all use earliest-end greedy.

Which tool for which job?

You needUse
Most meetings in one roomsort by end, keep each one that fits
Fewest points hitting every intervalsort by end, shoot at each uncovered end
Fewest removals to stop overlapsn minus the most you can keep
Fewest rooms for all meetingssort by start plus a min-heap of end times (= max overlap)
Most meetings in k roomssort by end, use the latest-ending room that's free in time
Most total valueweighted interval scheduling (DP)

Common mistakes

MistakeWhat happensDo this instead
Sorting by startone long meeting blocks two short onessort by end
Shortest firsta short meeting can block two otherssort by end
The wrong boundaryback-to-back meetings counted as conflicts, or the reverseread whether touching counts
a[1] - b[1] as a comparatorfar-apart balloons need 1 arrow instead of 2Integer.compare
Counting greedy with valuesmaximizes count, not valueDP over end-sorted intervals

Check yourself

Try to answer before opening each one - recalling beats re-reading.

1. Why sort by end time rather than start time?

Show answer

The earliest-ending interval leaves the resource free soonest, so it never blocks anything an alternative first choice wouldn't - the exchange argument. Earliest start can pick a long interval: {[1, 10], [2, 3], [4, 5]} gives 1 instead of 2.

2. [1, 2], [2, 3], [3, 4]: is the answer 2 or 3?

Show answer

It depends on the problem's rule. If a meeting must start strictly after the previous one ends (N Meetings in One Room), [1, 2] and [2, 3] conflict and the answer is 2. If back-to-back is allowed, it's 3. The comparison (> or >=) encodes that rule.

3. Why can (a, b) -> a[1] - b[1] sort intervals wrongly?

Show answer

The subtraction overflows for values far apart: -2147483647 - 2147483647 wraps to 2, so the comparator claims the smaller end is larger. Integer.compare(a[1], b[1]) compares without subtracting.

Key takeaways

  • Most non-overlapping intervals: sort by end, take each one that starts after the last taken ends.
  • Earliest start and shortest first both fail; prove the earliest end with an exchange argument.
  • Touching intervals: > or >= is the problem's rule - read it.
  • Fewest stabbing points (arrows) uses the same sort; fewest removals = n - maximum kept.
  • Compare with Integer.compare, never by subtracting.

Quick revise

The whole skill on one page, for the night before an interview.

This page puts the lesson and the deep dive together. Skim it before practice or an interview, and go back to the full lesson for anything you don't recognize.

Pick the tool

You needUse
Most meetings, one roomsort by end
Fewest arrowssort by end, shoot at ends
Fewest removalsn - most kept
Fewest roomsstart order plus a min-heap of ends
k roomslatest free room
Valuesweighted DP

Snippets worth knowing by heart

Java

Arrays.sort(iv, (a, b) -> Integer.compare(a[1], b[1]));   // never a[1] - b[1]
int kept = 0, end = Integer.MIN_VALUE;
for (int[] m : iv) if (m[0] >= end) { kept++; end = m[1]; }   // >= : back-to-back allowed

Python

import heapq
ends = []                                # fewest rooms
for s, e in sorted(meetings):
    if ends and ends[0] <= s:
        heapq.heapreplace(ends, e)
    else:
        heapq.heappush(ends, e)
rooms = len(ends)

The traps

  • Sorting by start or length: one long or badly placed meeting blocks two others. Sort by end time.
  • The boundary rule: back-to-back meetings count as conflicts, or the reverse. Read whether touching ends overlap.
  • Subtraction comparators: a[1] - b[1] overflows for far-apart values and sorts wrongly. Use Integer.compare.
  • Values with a count greedy: it maximizes how many, not how much. Use DP over end-sorted intervals.

Staff-level points, one line each

  • Earliest start kept 1 of [(0,10), (1,2), (3,4)], and shortest-first kept 1 of [(0,5), (4,7), (6,11)]. Earliest end kept 2 of each.
  • The heap's room count equaled the maximum overlap on 500 sets.
  • k rooms with "latest free room" matched brute force on 300 two-room cases.
  • Subtraction comparator: 1 arrow for two far-apart balloons; Integer.compare: 2.

Say it in an interview

  • "Sort by end time and keep everything that fits; the earliest finish leaves the most room."
  • "Fewest rooms is the dual: a heap of end times, and the answer is the maximum overlap."
  • "I'd use Integer.compare, never subtraction."
  • "With values, it becomes weighted interval scheduling."

Practice

This skill comes with 8 multiple-choice questions that tell you which misconception a wrong answer reveals and 2 graded problems. Your answers are graded and your mastery is tracked, so you'll know when the skill holds up. Running code and saving progress need a free account.

Practice Activity Selection Greedy free

N Meetings in One Room

MediumProCoding problemJava · Python

You are given the start and end times of n meetings, all needing the same single room. A meeting can be held only if the room is free for its entire [start, end] duration, and a meeting may start only strictly after the previous one ends: a meeting ending at 2 and one starting…

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

Minimum Arrows to Burst Balloons

MediumProCoding problemJava · Python

Each balloon spans an interval [start, end] along a wall. One arrow shot at position x pops every balloon with start <= x <= end, including any whose edge is exactly at x. Return the fewest arrows that pop them all.

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

Practice Activity Selection Greedy free

No card needed. Lessons, practice questions and easy problems are free forever.

Free plan, no card · 7-day Pro trial · Cancel anytime