Non-overlapping Intervals
Table of Contents + β
Non-overlapping Intervals flips the interval pattern around. Instead of joining overlaps, you delete some blocks so that none of them clash. The catch is you must delete as few as possible. This is a famous greedy question. Once you spot the trick, the code is tiny.
π― The Problem
You get a list of intervals and delete as few as possible so none clash.
- An interval is a pair: a start and an end. Some of them overlap.
- Remove the smallest number of intervals so the ones left never overlap.
- Return that count of removals.
- Two blocks overlap when one starts before the other ends.
- Touching at a point does not count. One ending at
2and the next starting at2can both stay.
Input: intervals = [[1, 2], [2, 3], [3, 4], [1, 3]]Output: 1
Explanation: Remove [1, 3] and the rest [[1, 2], [2, 3], [3, 4]] never overlap.So only one removal is needed.Here is the input drawn on a number line. The block [1, 3] cuts across both [1, 2] and [2, 3], so it is the troublemaker to drop.
You want to keep the most blocks you can, which is the same as removing the fewest.
π’ Approach 1: Try Every Subset (Brute Force)
Test every possible clash-free group and keep the biggest.
The idea:
- Look at every subset of blocks.
- Keep only the subsets where no two blocks overlap.
- Find the biggest clash-free group. Removals are total minus that group size.
Why it is weak:
- With n blocks there are 2 to the power n subsets.
- That is exponential time.
- It becomes useless past a handful of blocks.
Here is the try-every-subset code:
def erase_overlap_intervals(intervals): n = len(intervals) best_keep = 0
for mask in range(1 << n): chosen = [intervals[i] for i in range(n) if mask & (1 << i)] chosen.sort() if all(chosen[i][0] >= chosen[i - 1][1] for i in range(1, len(chosen))): best_keep = max(best_keep, len(chosen))
return n - best_keepβ‘ Approach 2: Greedy, Sort By End (Best)
The idea in one line: always keep the block that ends soonest, because it leaves the most room for the rest.
The idea:
- Sort by end, not by start.
- A block that ends early frees the timeline early.
- This is a greedy choice: best local pick at each step. It is the classic activity selection idea.
How it works:
- Remember the end of the last block you kept.
- For each next block, ask: does it start before that kept end?
- If yes, it overlaps, so remove it and add one to the count. Leave the kept end alone.
- If no, it fits, so keep it and move the kept end to this blockβs end.
Why it is fast:
- Sorting by end costs O(n log n). The sweep is O(n).
- The total is O(n log n), a huge jump from exponential.
Here is the greedy decision for each block after sorting by end.
Steps to Solve
- Sort the intervals by their end value.
- Set the kept end to the end of the first interval. Start the removal count at zero.
- Walk through the rest. For each block, compare its start with the kept end.
- If the start is less than the kept end, they overlap, so remove this block and add one to the count.
- If the start is not less than the kept end, keep it and set the kept end to this blockβs end.
- Return the removal count.
This Python version sorts by end, then sweeps once and counts removals.
def erase_overlap_intervals(intervals): intervals.sort(key=lambda block: block[1]) # sort by end kept_end = intervals[0][1] removals = 0
for start, end in intervals[1:]: if start < kept_end: # overlaps the kept block removals += 1 # so remove this one else: kept_end = end # it fits, keep it return removals
intervals = [[1, 2], [2, 3], [3, 4], [1, 3]]print(erase_overlap_intervals(intervals))The output of the above code will be:
1Now let us walk through the Python version line by line and see why each part is written this way.
def erase_overlap_intervals(intervals): intervals.sort(key=lambda block: block[1]) kept_end = intervals[0][1] removals = 0We sort by block[1], the end of each block. Sorting by end is the heart of the greedy idea. The block that ends earliest leaves the most room, so we keep it first. We store its end in kept_end. That is the wall the next blocks must clear.
for start, end in intervals[1:]: if start < kept_end: removals += 1We loop from the second block. The check start < kept_end reads as βthis block starts before the kept block ends.β So they clash. We must remove one of them. We already kept the one that ends earlier, so we drop this new one. We add one to removals and we do not touch kept_end, because the kept block is still the best wall.
else: kept_end = end return removalsIf the start is not before kept_end, this block fits cleanly after the kept one. So we keep it and move the wall to its end. At the end removals holds the fewest deletions needed.
β±οΈ Time and Space Complexity
The brute force checks every subset, so it is exponential and only fine for toy inputs. The greedy sorts by end in O(n log n), then sweeps in O(n). The sort dominates, so the total is O(n log n). Space is O(1) beyond the input, since we only track one end value and one counter. The real lesson is that sorting by end turns a hard removal problem into a simple count.
| Approach | Time Complexity | Space Complexity |
|---|---|---|
| Brute force, try all subsets | O(2^n) | O(n) |
| Greedy, sort by end | O(n log n) | O(1) |
Tip
Watch the sort key. Merge Intervals sorts by start, but Non-overlapping Intervals sorts by end. Sorting by end is what makes the greedy keep the most blocks.
π§© Key Takeaways
- β We delete the fewest blocks, which is the same as keeping the most.
- β Sort by end, because a block that ends early leaves more room for others.
- β Keep one wall, the end of the last kept block.
- β If a block starts before the wall, it clashes, so remove it and count it.
- β This greedy runs in O(n log n) time and uses almost no extra space.
Check Your Knowledge
Test what you learned. Pick an answer for each question, then click Check.
- 1
What does Non-overlapping Intervals ask you to return?
Why: You return the minimum number of removals needed so the remaining intervals never overlap.
- 2
Why does the greedy solution sort by end instead of start?
Why: Keeping the block that ends soonest frees the timeline earliest, which lets us keep the most blocks.
- 3
During the sweep, when do we remove a block?
Why: If the new block starts before the kept block ends, they overlap, so we remove the new one and keep the earlier-ending block.
- 4
What is the time complexity of the greedy solution?
Why: Sorting by end costs O(n log n) and dominates the linear sweep, so the total is O(n log n).