Skip to content

Coding patternsBlind 75

Merge Intervals

Sort by start, and each interval either stretches the last merged one or starts a new one. Solved two ways, merging pairs until nothing changes in O(n³) and sort then sweep in O(n log n), with a step-through, code in five languages and the Big O worked out from the code.

You are given a list intervals of n intervals [start, end], in any order (n is the length of intervals). Merge every group of overlapping intervals into one, and return the intervals that are left. Two intervals overlap if they share at least one point, so [2, 5] and [5, 7] overlap.

Example 1

Input: intervals = [[8, 10], [1, 4], [3, 6], [12, 14], [9, 11]]

Output: [[1, 6], [8, 11], [12, 14]]

[1, 4] and [3, 6] become [1, 6]. [8, 10] and [9, 11] become [8, 11]. [12, 14] touches nothing.

result[1, 6][8, 11][12, 14]02468101214

Example 2

Input: intervals = [[2, 5], [5, 7]]

Output: [[2, 7]]

They share the point 5, so they merge.

Example 3

Input: intervals = [[1, 9], [3, 4]]

Output: [[1, 9]]

[3, 4] sits inside [1, 9], so it adds nothing.

  • 1 ≤ n ≤ 10,000
  • 0 ≤ start ≤ end ≤ 10,000
Is the input sorted?
No. The intervals can come in any order.
Do touching intervals merge?
Yes. [2, 5] and [5, 7] share 5, so they become [2, 7].
Does the output order matter?
Any order is accepted. Sorting by start gives it for free.
Can one interval contain another?
Yes, like [3, 4] inside [1, 9]. The merged end must be the larger end, not the last one.

A list of ranges and a question about overlaps: that is the intervals pattern, and it nearly always starts with sorting by start. After sorting, any interval that overlaps the ones before it must overlap the last merged one. So one pass, comparing with just the last result, is enough.

This problem has two approaches. Once the list is sorted, there is no step between them.

Sort and sweep O(n log n)

Sort the intervals by start. Put the first one in the result. Then walk the rest:

  • If an interval starts no later than the last result ends, they overlap. Stretch the last result’s end to the larger of the two ends.
  • Otherwise there is a gap. Start a new result with this interval.

Why it is correct: after sorting, every interval after the gap starts even later. So none of them can reach back and touch a result that is already closed.

intervals[8, 10][1, 4][3, 6][12, 14][9, 11]02468101214
result
empty
  1. intervals.sort(key=lambda interval: interval[0])
  2. result = [intervals[0]]
  3. for start, end in intervals[1:]:
  4. if start <= result[-1][1]:
  5. result[-1][1] = max(result[-1][1], end)
  6. else:
  7. result.append([start, end])
  8. return result

The intervals are in no order, so overlapping ones can be far apart in the list.

1 / 8
Solution.javasolution.pySolution.cssolution.gosolution.ts
class Solution {
// Sort and sweep: after sorting by start, an interval can only overlap the
// last merged one. The sort costs O(n log n), the sweep O(n).
public int[][] merge(int[][] intervals) {
java.util.Arrays.sort(intervals, (first, second) -> Integer.compare(first[0], second[0]));
java.util.List<int[]> result = new java.util.ArrayList<>();
result.add(new int[] { intervals[0][0], intervals[0][1] });
for (int k = 1; k < intervals.length; k++) {
int[] last = result.get(result.size() - 1);
int start = intervals[k][0], end = intervals[k][1];
if (start <= last[1]) {
// max, not end: this interval may sit inside the last one.
last[1] = Math.max(last[1], end);
} else {
// A gap. Later intervals start even later, so the last one is closed.
result.add(new int[] { start, end });
}
}
return result.toArray(new int[0][]);
}
}
Time O(n log n)Space O(n)Here is how we get these from the code, in four steps.
  1. 1 Find the work that repeats

    Two parts repeat. The sort compares intervals many times. Then the loop does one comparison and one stretch or append per interval, which is 1 step per run.

    1. intervals.sort(key=lambda interval: interval[0])
    2. result = [intervals[0]]
    3. for start, end in intervals[1:]:
    4. if start <= result[-1][1]:
    5. result[-1][1] = max(result[-1][1], end)
    6. else:
    7. result.append([start, end])
    8. return result
  2. 2 Count the runs on Example 1

    Example 1 has 5 intervals. Sorting 5 items takes about 5 × log₂ 5, so about 12 steps. The loop then handles the 4 intervals after the first.

    12 + 4 = about 16 steps.

    partsortmerge loop
    stepsabout 5 × log 5 ≈ 124
  3. 3 Write the count for any n

    n log₂ n (sort) + (n − 1) (loop)

    Check it with Example 1: 5 × 2.3 + 4 ≈ 16.

  4. 4 Keep only the part that grows fastest

    Keep the sort. log n keeps growing, so n log n pulls away from the loop’s n − 1. What is left is n log n.

    So the time is O(n log n).

  5. S Space: count the extra memory the same way

    The result can hold up to n intervals, when nothing overlaps. Sorting also needs some memory in most languages. The space is O(n).

    result (up to n intervals)sort space

2,000 random lists of 1 to 7 intervals with ends from 0 to 10, so touching ends, nested intervals, repeats and zero-length intervals come up often. Merge until stable and sort and sweep gave the same answer every time.

Largest inputsResult
10,000 intervals (the most allowed), shuffled, none touching, so nothing merges. Sort and sweep.0.004 s, and about 0.8 MB of extra memory: the result holds all 10,000 intervals, plus the sort.
10,000 intervals, shuffled, that all chain into one. Sort and sweep.0.003 s, and about 0.15 MB, mostly for the sort.
100 intervals that chain into one. Merge until stable.Under 0.001 s. It is O(n³), so at 10,000 intervals it could take about a trillion steps.