Non-overlapping Intervals – Solution & Complexity

Solution Walkthrough

1. Understand the Goal

  • Find the minimum number of intervals to delete so that none of the survivors overlap — equivalently, find the MAXIMUM number of mutually non-overlapping intervals you can keep, then subtract that count from the total.
  • Intervals that only touch at an endpoint (like [1,2] and [2,3]) count as non-overlapping and may both survive.

2. Choose the Core Pattern

  • This is "activity selection": sort by end time and greedily keep the interval that finishes earliest whenever a conflict is found.
  • The exchange argument: among any set of intervals that overlap, whichever one ends earliest leaves the most room for everything after it — swapping any other choice for the earliest-ending one can never make the remaining selection worse, so the greedy choice is always at least as good as any alternative.

3. Build the Algorithm

  • Sort all intervals by end time ascending.
  • Track lastEnd, the end time of the most recently kept interval, starting at negative infinity (or the first interval's end after keeping it).
  • Scan left to right: if the current interval's start is >= lastEnd, it doesn't overlap the last kept interval — keep it and update lastEnd to its end. Otherwise it overlaps, so it must be removed; do not update lastEnd (the interval already kept still ends earlier, so it stays the better anchor).
  • The answer is total intervals - kept count.

4. Check Edge Cases

  • 0 or 1 intervals: nothing can overlap, so 0 removals.
  • All intervals identical or fully nested: only the true earliest-ending interval survives per overlapping cluster; every other overlapping one gets removed.
  • Intervals that only touch at endpoints ([1,2], [2,3]): the >= comparison (not >) on start vs. lastEnd must treat these as non-overlapping, or the count will be wrong.

5. Final Solution and Complexity

  • Time complexity is O(n log n).
  • Space complexity is O(1) beyond the sort output model.
def erase_overlap_intervals(intervals: list[list[int]]) -> int:
    if not intervals:
        return 0
    intervals.sort(key=lambda interval: interval[1])
    removals = 0
    prev_end = intervals[0][1]
    for start, end in intervals[1:]:
        if start < prev_end:
            removals += 1
        else:
            prev_end = end
    return removals

FAQ