Non-overlapping Intervals – Solution & Complexity

Solution Walkthrough

  1. Step 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. Step 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. Step 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. Step 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. Step 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