Non-overlapping Intervals
Return the minimum number of intervals to remove so that the rest are non-overlapping.
Open on LeetCode ↗Intuition
Almost everyone sorts by start time first, and it fails — one long interval beginning early crowds out several short ones that would all have fit. Sort by end instead. Removing the fewest is the same as keeping the most, which makes this classic activity selection: whichever interval finishes earliest leaves the most room for everything after it, so keeping it never costs you a better answer. Then greedily keep any interval starting at or after the last kept end.
Classic activity selection: sort by end time and keep every interval that starts after the last kept one ends. Ending earliest leaves the most room for what follows, which is the exchange argument that makes this greedy optimal.
Approach
Flip the objective
Minimising removals is maximising the size of a non-overlapping subset. Stated that way it is the meeting-rooms / activity-selection problem, which has a known greedy solution — recognising the equivalence is most of the work.
Sort by end time, not start
Sorting by start fails: one very long interval starting early can crowd out several short ones. Sorting by end works because among any set of conflicting intervals, keeping the one that finishes earliest dominates — it leaves a superset of the room any other choice would leave, so no optimal solution is ever lost.
Sweep and count conflicts
Track lastEnd, the end of the most recently kept interval. If the next interval starts at or after it, keep it and update lastEnd. Otherwise it clashes — increment the removal count and, crucially, drop this interval rather than the kept one, since the kept one ends no later and is therefore never the worse choice. O(n log n) for the sort, O(n) for the sweep.
Solution & live demo
Common pitfalls
Sorting by start time
intervals.sort(key=lambda x: x[0])
intervals.sort(key=lambda x: x[1])
A long interval starting early blocks everything behind it. Sorting by end time means each kept interval frees the maximum remaining space, which is exactly what maximises the count kept.
Treating touching intervals as overlapping
if s > lastEnd:
if s >= lastEnd:
[1,2] and [2,3] share only an endpoint and can both be kept. The strict test removes one unnecessarily and reports a higher removal count than needed.
Updating lastEnd when dropping an interval
else:
removed += 1
lastEnd = eelse:
removed += 1A removed interval isn't in the schedule, so it can't constrain what comes next — and since the list is sorted by end, its end is no earlier than the one already kept. Updating would only make the frontier worse.
Edge cases
Every interval is kept and 0 is returned.
One is kept and the remaining n - 1 are removed.
Not an overlap under this problem's definition, so the >= comparison keeps both.
Nothing to remove; the answer is 0.