Quick Overview

Merge a list of closed integer intervals so that any intervals sharing at least one point, including intervals that only touch at an endpoint, become a single interval, then return the disjoint result sorted by start. Tests careful handling of unsorted input and of nested, touching and duplicate intervals, with an efficient solution for up to 10^5 intervals.

Merge Overlapping or Touching Closed Intervals into a Sorted Disjoint List

Company: Fractal

Role: Software Engineer

Category: Coding & Algorithms

Difficulty: medium

Interview Round: Technical Screen

Given a list of closed intervals on the integer number line, merge every group of intervals that overlap or touch into a single interval, and return the resulting disjoint intervals sorted by their start. The report identifies the problem only as a standard interval-merging exercise; this version fixes the exact merging and ordering rules in Rules below. ### Function Signature ```python def merge_intervals(intervals: list[list[int]]) -> list[list[int]]: ``` ### Rules - Each element of `intervals` is a pair `[start, end]` with `start <= end`, describing every point `x` with `start <= x <= end`. An interval with `start == end` is a single point. - Two intervals `[a, b]` and `[c, d]` share a point when `c <= b` and `a <= d`. Intervals that share a point belong to the same group, and so do intervals connected through a chain of such pairs. - Intervals that only touch at an endpoint, such as `[1, 4]` and `[4, 6]`, share that point and are merged. Intervals such as `[1, 2]` and `[3, 4]` share no point and stay separate, even though no integer lies between them. - Each group becomes one interval `[smallest start in the group, largest end in the group]`. - The input may be in any order and may contain duplicate or nested intervals. - Return the merged intervals as a list of `[start, end]` pairs in increasing order of `start`. Because the merged intervals are disjoint, this order is unique. ### Constraints - `1 <= len(intervals) <= 10^5` - `-10^9 <= start <= end <= 10^9`; every value fits in a 32-bit signed integer. ### Examples **Example 1** ```text Input: intervals = [[5, 8], [1, 3], [2, 4], [10, 12]] Output: [[1, 4], [5, 8], [10, 12]] ``` `[1, 3]` and `[2, 4]` share the points from 2 to 3 and merge into `[1, 4]`. `[5, 8]` starts after 4, so it stays separate, and `[10, 12]` shares no point with any other interval. **Example 2** ```text Input: intervals = [[4, 7], [1, 4], [7, 7]] Output: [[1, 7]] ``` `[1, 4]` and `[4, 7]` touch at 4, and the single point `[7, 7]` touches `[4, 7]` at 7, so all three intervals form one group. **Example 3** ```text Input: intervals = [[-3, 0], [2, 9], [-5, -4], [3, 5], [-4, -4]] Output: [[-5, -4], [-3, 0], [2, 9]] ``` `[-4, -4]` touches `[-5, -4]` at -4, and `[3, 5]` lies inside `[2, 9]`. `[-3, 0]` starts after -4 and ends before 2, so it stays on its own.

Overview: Merge a list of closed integer intervals so that any intervals sharing at least one point, including intervals that only touch at an endpoint, become a single interval, then return the disjoint result sorted by start. Tests careful handling of unsorted input and of nested, touching and duplicate intervals, with an efficient solution for up to 10^5 intervals.

Given a list of closed intervals on the integer number line, merge every group of intervals that overlap or touch into a single interval, and return the resulting disjoint intervals sorted by their start. Implement `merge_intervals(intervals)`, where `intervals` is a list of pairs `[start, end]`, and return a list of `[start, end]` pairs. ### Rules - Each pair `[start, end]` has `start <= end` and describes every point `x` with `start <= x <= end`. An interval with `start == end` is a single point. - Two intervals `[a, b]` and `[c, d]` share a point when `c <= b` and `a <= d`. Intervals that share a point belong to the same group, and so do intervals connected through a chain of such pairs. - Intervals that only touch at an endpoint, such as `[1, 4]` and `[4, 6]`, share that point and are merged. Intervals such as `[1, 2]` and `[3, 4]` share no point and stay separate, even though no integer lies between them. - Each group becomes one interval `[smallest start in the group, largest end in the group]`. - The input may be in any order and may contain duplicate or nested intervals. - Return the merged intervals as a list of `[start, end]` pairs in increasing order of `start`. Because the merged intervals are disjoint, this order is unique. ### Constraints - `1 <= len(intervals) <= 10^5` - `-10^9 <= start <= end <= 10^9`; every value fits in a 32-bit signed integer (no value exceeds 2^31 - 1, so Java and C++ use `int`). ### Example 1 ```text Input: intervals = [[5, 8], [1, 3], [2, 4], [10, 12]] Output: [[1, 4], [5, 8], [10, 12]] ``` `[1, 3]` and `[2, 4]` share the points from 2 to 3 and merge into `[1, 4]`. `[5, 8]` starts after 4, so it stays separate, and `[10, 12]` shares no point with any other interval. ### Example 2 ```text Input: intervals = [[4, 7], [1, 4], [7, 7]] Output: [[1, 7]] ``` `[1, 4]` and `[4, 7]` touch at 4, and the single point `[7, 7]` touches `[4, 7]` at 7, so all three intervals form one group.

Constraints

  • 1 <= len(intervals) <= 10^5
  • -10^9 <= start <= end <= 10^9 for every pair [start, end]; every value fits in a 32-bit signed integer (no value exceeds 2^31 - 1), so Java and C++ use int.
  • The input may be in any order and may contain duplicate, nested, or single-point (start == end) intervals.

Examples

Input: ([[5, 8], [1, 3], [2, 4], [10, 12]],)

Expected Output: [[1, 4], [5, 8], [10, 12]]

Explanation: Source example 1: [1, 3] and [2, 4] overlap into [1, 4]; [5, 8] starts after 4 and [10, 12] is isolated.

Input: ([[4, 7], [1, 4], [7, 7]],)

Expected Output: [[1, 7]]

Explanation: Source example 2: [1, 4] and [4, 7] touch at 4 and the point [7, 7] touches [4, 7], so all three form one group.

Hints

  1. Two intervals [a, b] and [c, d] share a point exactly when c <= b and a <= d: touching at an endpoint counts, but [1, 2] and [3, 4] share nothing even though no integer lies between them.
  2. Grouping is transitive: two intervals that share no point with each other can still land in the same group through a third interval that overlaps both.
  3. A finished group is described only by its smallest start and its largest end, and the answer must list the groups in increasing order of start.

Loading coding console...

Show the approach

Approach

Sort the intervals by start, then sweep once while holding the current group as a running pair [cur_start, cur_end]. For each next interval [s, e]: if s <= cur_end it shares a point with the current group, so cur_end becomes max(cur_end, e); otherwise the current group is closed and emitted, and [s, e] starts a new group. Invariant: after any prefix of the sorted list, the emitted intervals plus [cur_start, cur_end] are exactly the merged groups of that prefix, and the current group's union is the whole segment [cur_start, cur_end]. Correctness: starts are non-decreasing, so s >= cur_start; if s <= cur_end then s lies inside the current group's union, so some interval of the group contains s and shares that point with [s, e]. If s > cur_end, every later interval also starts after cur_end, so nothing can ever join the closed group, and emitting groups in sweep order yields increasing starts. Edge cases: a single interval or single point is returned as is; duplicates and equal starts merge; touching endpoints (s == cur_end) merge while a gap between consecutive integers (s == cur_end + 1) does not; a nested interval with a smaller end must not shrink cur_end, which is why the end is a maximum rather than an assignment; all values stay within 32-bit range and no arithmetic is done on them (the Java comparator uses Integer.compare, and the JavaScript difference of two starts is at most 2 * 10^9, exact in a double).

Time complexity:
O(n log n)
Space complexity:
O(n)