Non-overlapping Intervals
You are given an array of intervals where each is [start, end]. Return the minimum number of intervals you must remove so that none of the remaining intervals overlap. Intervals that merely touch at an endpoint (one ends exactly where the next begins) are not considered overlapping and may both stay.
- 1 ≤ intervals.length ≤ 10⁵
- intervals[i].length == 2
- -5 × 10⁴ ≤ start < end ≤ 5 × 10⁴
- Touching at an endpoint is allowed; only intervals sharing interior points conflict
intervals = [[1,2],[2,3],[3,4],[1,3]]1intervals = [[1,2],[1,2],[1,2]]2intervals = [[1,2],[2,3]]0intervals = [[1,100],[11,22],[1,11],[2,12]]2intervals = [[1,2]]0"Remove the fewest intervals so the rest do not overlap" sounds like a deletion problem, but the first move is to invert it: removing the fewest is the same as keeping the most. If you can identify the largest set of intervals that already avoid each other, then everything else is what you delete, and its size is minimal by definition.
So the real task is: find the maximum number of mutually non-overlapping intervals, then the answer is total − kept.
And "maximum non-overlapping intervals" is a problem you have already solved — it is exactly Activity Selection. This is that problem wearing a different question.
When several intervals overlap and you can keep only one, which survives? Run the same test of tempting rules as everywhere in greedy:
This is the Activity Selection greedy, and it is justified by the same exchange argument: among any set of conflicting intervals, replacing whichever the optimal solution kept with the earliest-finishing one never creates a new conflict (it ends no later, so nothing that followed is disturbed) and never reduces the count. So keeping the earliest-finishing interval is always safe.
Sort by end time. Sweep, tracking the end of the last interval you decided to keep. For each interval:
def erase_overlap_intervals(intervals):
intervals.sort(key=lambda x: x[1]) # sort by END
removals = 0
last_end = float('-inf')
for start, end in intervals:
if start >= last_end: # no conflict: keep it
last_end = end
else: # conflict: remove this one
removals += 1
return removalsSorting is O(N log N); the sweep is O(N).
start >= last_end (keep) versus start < last_end (remove), because touching does not count as overlap here — the reverse of the Merge Intervals convention. Getting either the key or the boundary wrong produces a plausible but wrong count.[1,2], [2,3], [1,3], [3,4].[1,2]: start 1 ≥ −∞. Keep. last_end = 2.[2,3]: start 2 ≥ 2 (touching is allowed). Keep. last_end = 3.[1,3]: start 1 < 3. Conflict — remove. removals = 1.[3,4]: start 3 ≥ 3. Keep. last_end = 4.Answer 1 — remove [1,3], and [1,2],[2,3],[3,4] remain disjoint (touching endpoints and all).
And [[1,2],[1,2],[1,2]]: sorted, keep the first, the next two both start at 1 < last_end 2, so both are removed → 2.
Two ideas to carry forward.
Invert "minimise removals" into "maximise keeps". Many deletion or destruction problems are really selection problems in disguise, and the selection version is often a clean greedy. Whenever you are asked to remove the fewest things to satisfy a property, ask what the largest surviving set looks like — the complement is usually easier to reason about and to prove optimal.
Match the sort key to the question, not to the data type. Both this problem and Merge Intervals take a list of intervals, yet one sorts by end and the other by start, because they ask fundamentally different things — one selects a maximum disjoint subset, the other fuses touching ranges. The interval "pattern" is not one algorithm; it is a family, and choosing the key is choosing the member. When you recognise an interval problem, your first question should be "am I selecting or merging?" — selecting points to sort-by-end and the Activity Selection greedy, merging points to sort-by-start and the anchor sweep.
Together with Activity Selection, this problem shows the same greedy answering two phrasings — most kept, fewest removed — which is worth remembering as a single reusable tool rather than two separate tricks.
Finish Line Greedy
CORE LOGIC
Always prioritize the interval that ends first. This leaves the most remaining time for other activities.
REMOVAL RULE
If an interval starts before the previous finish line, it MUST be removed.