AlgoMaster Logo

Non-overlapping Intervals

Last Updated: March 29, 2026

medium
4 min read

Understanding the Problem

We have a collection of intervals on a number line, and some of them overlap. Our job is to figure out the fewest intervals we can throw away so that no two remaining intervals share any interior points. Two intervals touching at exactly one point (like [1,2] and [2,3]) are fine.

Think of it like scheduling meetings in a single conference room. Each interval is a meeting time. If two meetings overlap, we have to cancel one. The question is: what's the minimum number of meetings to cancel so everything fits without conflict?

The key observation is that "minimum removals" is the same as "maximum intervals we can keep." If we can find the largest set of non-overlapping intervals, then the number of removals is simply the total count minus that maximum set size. This reframes the problem into the classic "Activity Selection" problem from greedy algorithms.

Key Constraints:

  • 1 <= intervals.length <= 10^5 → With n up to 100,000, an O(n^2) solution would be too slow. We need O(n log n) or better.
  • -5 * 10^4 <= start_i < end_i <= 5 * 10^4 → Intervals can have negative start/end values. The strict inequality guarantees every interval has positive length.
  • intervals[i].length == 2 → Each interval is exactly a pair. No degenerate inputs to worry about.

Approach 1: Greedy (Sort by Start)

Intuition

A natural first thought: sort the intervals by their start time, then walk through them left to right. If the current interval overlaps with the previous one we kept, we need to remove one of them. But which one should we remove?

Here's the key insight: when two intervals overlap, we should remove the one that extends further to the right (has the larger end value). Why? Because keeping the one with the smaller end value leaves more room for future intervals to fit without conflict. It's a greedy choice: always keep the interval that "finishes sooner."

So the algorithm becomes: sort by start, track the end of the last kept interval, and whenever we see an overlap, keep whichever interval ends earlier and count one removal.

Algorithm

  1. Sort the intervals by their start value. If two intervals have the same start, sort by end value.
  2. Initialize prevEnd to the end of the first interval and removals = 0.
  3. For each subsequent interval [start, end]:
    • If start < prevEnd (overlap detected):
      • Increment removals.
      • Update prevEnd = min(prevEnd, end) (keep the interval that ends sooner).
    • Otherwise (no overlap):
      • Update prevEnd = end.
  4. Return removals.

Example Walkthrough

1Sorted by start: [[1,2],[1,3],[2,3],[3,4]]. Take [1,2], prevEnd=2, removals=0
[1, 2]
[1, 3]
[2, 3]
[3, 4]
14
1/5

Code

The sort-by-start approach works, but the overlap resolution requires a min comparison to decide which interval to keep. What if we sorted by end time instead, so the greedy choice becomes automatic?

Approach 2: Greedy (Sort by End) - Optimal

Intuition

Here's a cleaner way to think about the same greedy idea. Instead of sorting by start time and deciding which overlapping interval to remove, sort by end time. Now the greedy choice becomes trivially simple: always keep the next interval that starts at or after the previous kept interval's end.

Why does sorting by end time simplify things? Because the interval that ends earliest is always the best to keep. When we sort by end, the first non-overlapping interval we encounter is automatically the one that ends soonest. There's no need to compare ends and pick the minimum. We just keep or skip.

This is exactly the classic Activity Selection algorithm. The proof of optimality is well-known: at each step, choosing the earliest-finishing compatible activity never leads to a worse outcome than any other choice. This means we can count how many intervals we keep, and the answer is n - kept.

Algorithm

  1. Sort the intervals by end value. If two intervals have the same end, their order doesn't matter.
  2. Initialize prevEnd to the end of the first interval and kept = 1 (we keep the first interval).
  3. For each subsequent interval [start, end]:
    • If start >= prevEnd (no overlap):
      • Keep this interval: kept++, update prevEnd = end.
    • Otherwise: skip it (it overlaps).
  4. Return n - kept.

Example Walkthrough

1Sorted by end: [[1,2],[2,3],[1,3],[3,4]]. Keep [1,2], prevEnd=2, kept=1
[1, 2]
[2, 3]
[1, 3]
[3, 4]
14
1/5

Code