{x}
blog image

Merge Intervals

Given an array of intervals where intervals[i] = [starti, endi], merge all overlapping intervals, and return an array of the non-overlapping intervals that cover all the intervals in the input.

 

Example 1:

Input: intervals = [[1,3],[2,6],[8,10],[15,18]]
Output: [[1,6],[8,10],[15,18]]
Explanation: Since intervals [1,3] and [2,6] overlap, merge them into [1,6].

Example 2:

Input: intervals = [[1,4],[4,5]]
Output: [[1,5]]
Explanation: Intervals [1,4] and [4,5] are considered overlapping.

 

Constraints:

  • 1 <= intervals.length <= 104
  • intervals[i].length == 2
  • 0 <= starti <= endi <= 104

Solution Explanation for Merge Intervals

The problem asks to merge overlapping intervals. The most efficient approach uses sorting and a single pass.

Algorithm:

  1. Sort: Sort the intervals based on their start times. This is crucial for efficiently identifying overlapping intervals. We can use any efficient sorting algorithm; the built-in sort functions in most languages have a time complexity of O(n log n).

  2. Merge: Iterate through the sorted intervals. Maintain a variable to track the last merged interval.

    • If the current interval's start time is greater than the end time of the last merged interval, it means there's no overlap. Add the current interval to the result list as a new merged interval.
    • If the current interval overlaps with the last merged interval (current start <= last end), merge them by updating the end time of the last merged interval to the maximum of the current interval's end time and the last merged interval's end time.
  3. Return: After processing all intervals, return the result list containing the merged non-overlapping intervals.

Time Complexity Analysis:

  • Sorting the intervals takes O(n log n) time, where n is the number of intervals.
  • The single pass through the sorted intervals takes O(n) time.
  • Therefore, the overall time complexity is dominated by the sorting step, making it O(n log n).

Space Complexity Analysis:

  • The space complexity depends on the implementation. If we use an in-place sorting algorithm (some implementations are in-place), the space complexity would be O(1). However, many built-in sort functions use extra space, leading to O(log n) or even O(n) in the worst case (depending on the sorting algorithm used internally).
  • The result list stores the merged intervals, which can have at most n elements in the worst case (no overlapping intervals). Therefore, the space used by the result list is O(n).

Code Examples (with comments):

Python:

def merge(intervals):
    # Sort intervals by start time
    intervals.sort() 
    merged = []
    
    for interval in intervals:
        # If the list of merged intervals is empty or if the current interval does not overlap with the last merged interval
        if not merged or interval[0] > merged[-1][1]:
            merged.append(interval)  # Add the current interval as a new merged interval
        else:
            # Merge the current interval with the last merged interval
            merged[-1][1] = max(merged[-1][1], interval[1])
    return merged
 

Java:

import java.util.Arrays;
import java.util.ArrayList;
import java.util.List;
 
class Solution {
    public int[][] merge(int[][] intervals) {
        Arrays.sort(intervals, (a, b) -> a[0] - b[0]); // Sort by start time
 
        List<int[]> merged = new ArrayList<>();
        for (int[] interval : intervals) {
            if (merged.isEmpty() || interval[0] > merged.get(merged.size() - 1)[1]) {
                merged.add(interval);
            } else {
                merged.get(merged.size() - 1)[1] = Math.max(merged.get(merged.size() - 1)[1], interval[1]);
            }
        }
 
        return merged.toArray(new int[0][]); // Convert List to int[][]
    }
}

The other languages (C++, JavaScript, Go, etc.) would follow a very similar structure, with minor syntax differences. The core logic of sorting and iterating to merge remains the same.