Problem
Given a list of intervals in no particular order, merge every pair that overlaps and return the smallest set of non-overlapping intervals that covers the same ranges.
Signal
Unordered overlapping ranges that need collapsing is the canonical "sort by start, then sweep" shape — sorting turns an all-pairs overlap question into a single linear scan against just the previous result.
Approach
Sort intervals by start time. Walk through them keeping a running "last merged" interval: if the current interval's start is at or before the last merged interval's end, they overlap — extend the last merged interval's end to cover both. Otherwise, the current interval starts a new group — append it as-is.
Skeleton
sort intervals by start
result = [intervals[0]]
for interval in intervals[1:]:
if interval.start <= result[-1].end:
result[-1].end = max(result[-1].end, interval.end)
else:
result.append(interval)
Solution
def merge(intervals: list[list[int]]) -> list[list[int]]:
intervals.sort(key=lambda iv: iv[0])
result = [intervals[0]]
for start, end in intervals[1:]:
if start <= result[-1][1]:
result[-1][1] = max(result[-1][1], end)
else:
result.append([start, end])
return result
Complexity
O(n log n) time — dominated by the sort; the sweep itself is O(n). O(n) space for the sorted copy and result (O(1) extra if sorting in place and merging into it).
Pitfalls
- Comparing only starts and ends of adjacent pairs without sorting first — the overlap check only works because sorting guarantees you never need to look backward past the last merged interval.
- Using
<instead of<=for the overlap test — touching intervals like[1,4]and[4,5]are meant to merge in the standard version of this problem. - Taking
max(result[-1].end, interval.end)matters — a later interval can be fully contained inside the current merged range, and blindly overwriting the end with the new interval's end would shrink it incorrectly.