Merge Intervals
Anything with a start and an end — meetings, ranges, balloons — yields to the same move: sort by start, then sweep, comparing each interval only against the last one kept.
Sort, then handle overlaps one at a time
Unsorted intervals can overlap anywhere, so every pair might interact — O(n²) chaos. Sorted by start, an interval can only overlap the one merged region growing right before it. One comparison per interval, and the whole problem falls into a single pass.
The four ways two intervals can meet
For [a, b] and [c, d] with a ≤ c (guaranteed after sorting),
everything reduces to comparing b and c:
current.start <= previous.end. Note max(b, d) on merge —
containment means the earlier interval can already extend past the new one.
Five operations cover the whole pattern:
- Merge overlapping intervals.
- Insert a new interval into an existing sorted list.
- Intersect two lists of intervals.
- Find gaps between intervals (free time).
- Count overlaps (meeting rooms, max concurrent).
When to reach for it
| signal in problem | likely approach |
|---|---|
| "merge overlapping intervals" | Sort + merge |
| "insert interval" | Find overlap range, merge |
| "minimum meeting rooms" | Sort + min heap of end times |
| "can attend all meetings?" | Sort + check any overlap |
| "free time between meetings" | Merge, then find gaps |
| "interval intersection" | Two pointers on sorted lists |
| "minimum intervals to remove" | Greedy, keep non-overlapping |
| problem has "start" and "end" fields | Interval technique |
One skeleton, six problems
T1Merge Intervals — LC 56
def merge(intervals):
intervals.sort(key=lambda x: x[0]) # sort by start
merged = [intervals[0]]
for start, end in intervals[1:]:
if start <= merged[-1][1]: # overlap with last merged
merged[-1][1] = max(merged[-1][1], end) # extend end
else:
merged.append([start, end]) # no overlap, add new
return merged
T2Insert Interval — LC 57
The list is already sorted, so no re-sort — a three-phase scan: everything strictly before, everything overlapping (absorbed into the new interval), everything after. O(n).
def insert(intervals, newInterval):
result = []
i = 0
n = len(intervals)
# 1. Add all intervals that come BEFORE newInterval
while i < n and intervals[i][1] < newInterval[0]:
result.append(intervals[i])
i += 1
# 2. Merge all overlapping intervals with newInterval
while i < n and intervals[i][0] <= newInterval[1]:
newInterval[0] = min(newInterval[0], intervals[i][0])
newInterval[1] = max(newInterval[1], intervals[i][1])
i += 1
result.append(newInterval)
# 3. Add all intervals that come AFTER
while i < n:
result.append(intervals[i])
i += 1
return result
T3Non-overlapping Intervals — LC 435
Minimum removals so nothing overlaps. This one sorts by end time:
always keeping the interval that ends earliest leaves maximum room, so it maximizes the
kept set — removals are just n - kept.
def eraseOverlapIntervals(intervals):
"""Minimum intervals to remove so none overlap."""
intervals.sort(key=lambda x: x[1]) # sort by END time (greedy)
count = 0
prev_end = float('-inf')
for start, end in intervals:
if start >= prev_end:
# No overlap, keep this interval
prev_end = end
else:
# Overlap, remove this one (it ends later)
count += 1
return count
T4Meeting Rooms — LC 252
def canAttendMeetings(intervals):
"""Can a person attend all meetings (no overlaps)?"""
intervals.sort(key=lambda x: x[0])
for i in range(1, len(intervals)):
if intervals[i][0] < intervals[i - 1][1]:
return False # overlap found
return True
# [0,30] [5,10] [15,20] -> sorted: [0,30],[5,10],[15,20]
# 5 < 30? YES -> return False (can't attend all)
T5Meeting Rooms II — LC 253
Minimum rooms = maximum concurrent meetings. Keep a min heap of end times, one entry per room in use. The root is the room that frees up first — reuse it if it's done before the next meeting starts.
import heapq
def minMeetingRooms(intervals):
"""Minimum number of meeting rooms required."""
intervals.sort(key=lambda x: x[0]) # sort by start
heap = [] # min heap of end times (rooms in use)
for start, end in intervals:
# If earliest ending room is free, reuse it
if heap and heap[0] <= start:
heapq.heappop(heap)
# Assign a room (new or reused)
heapq.heappush(heap, end)
return len(heap) # heap size = rooms needed
The alternative — a sweep line: turn every interval into +1/−1 events and track the running count.
def minMeetingRooms_sweep(intervals):
events = []
for start, end in intervals:
events.append((start, 1)) # meeting starts: +1 room
events.append((end, -1)) # meeting ends: -1 room
events.sort() # sort by time, then by type (-1 before +1 at same time)
max_rooms = 0
current_rooms = 0
for time, delta in events:
current_rooms += delta
max_rooms = max(max_rooms, current_rooms)
return max_rooms
T6Interval List Intersections — LC 986
Two already-sorted lists → two pointers. The candidate intersection is
[max(starts), min(ends)]; advance whichever interval ends first
(it can't intersect anything else).
def intervalIntersection(firstList, secondList):
"""Find intersections of two sorted interval lists."""
result = []
i = j = 0
while i < len(firstList) and j < len(secondList):
# Find overlap
start = max(firstList[i][0], secondList[j][0])
end = min(firstList[i][1], secondList[j][1])
if start <= end:
result.append([start, end])
# Advance the pointer with the smaller end
if firstList[i][1] < secondList[j][1]:
i += 1
else:
j += 1
return result
T7Employee Free Time — LC 759
Flatten everyone's schedule, sort, merge — then the gaps between merged blocks are the common free time.
def employeeFreeTime(schedule):
"""Find common free time across all employees."""
# Flatten all intervals and sort
all_intervals = []
for employee in schedule:
for interval in employee:
all_intervals.append(interval)
all_intervals.sort(key=lambda x: x.start)
# Merge all intervals, then find gaps
result = []
prev_end = all_intervals[0].end
for interval in all_intervals[1:]:
if interval.start > prev_end:
result.append(Interval(prev_end, interval.start)) # gap = free time
prev_end = max(prev_end, interval.end)
return result
refComplexity reference
| problem | time | space | notes |
|---|---|---|---|
| Merge Intervals | O(n log n) | O(n) | Sort dominates |
| Insert Interval | O(n) | O(n) | Already sorted, single pass |
| Non-overlapping (removals) | O(n log n) | O(1) | Sort by end, greedy |
| Meeting Rooms | O(n log n) | O(1) | Sort + single pass |
| Meeting Rooms II (heap) | O(n log n) | O(n) | Sort + heap operations |
| Meeting Rooms II (sweep) | O(n log n) | O(n) | Sort events |
| Interval Intersections | O(n + m) | O(1)* | Two pointers, already sorted |
| Employee Free Time | O(n log n) | O(n) | Flatten + sort + merge |
* output space not counted.
Where intervals go wrong
[1,5] and [5,10] overlap? Depends on the problem.
Merging touching intervals: start <= prev_end (inclusive).
Meeting rooms: start < prev_end — back-to-back meetings don't conflict
if one ends exactly as the other starts.
[1,10] holding
[3,5] — this is why the merge extends with max(prev_end, end),
never just end.
Practice ladder
In this order — easiest first. Tick each one as you solve it, and re-solve anything that took more than 25 minutes two days later.