Taro Logo

Merge Intervals

Medium
1 views
8 years ago

You are given an array of intervals, where each interval is represented as a pair of integers [start, end]. Your task is to merge all overlapping intervals and return a new array of non-overlapping intervals that cover all the intervals in the input.

For example, if you have the intervals [[1,3],[2,6],[8,10],[15,18]], the output should be [[1,6],[8,10],[15,18]]. The intervals [1,3] and [2,6] overlap, so they are merged into [1,6].

Here are a few more examples to illustrate the problem:

  • Example 1:
    • Input: [[1,4],[4,5]]
    • Output: [[1,5]] (The intervals [1,4] and [4,5] are considered overlapping.)
  • Example 2:
    • Input: [[1,3],[2,6],[8,10],[15,18]]
    • Output: [[1,6],[8,10],[15,18]]
  • Example 3:
    • Input: [[1,4],[0,4]]
    • Output: [[0,4]]
  • Example 4:
    • Input: [[1,4],[0,0]]
    • Output: [[0,0],[1,4]]
  • Example 5:
    • Input: [[1,4],[0,2],[3,5]]
    • Output: [[0,5]]

Write a function mergeIntervals(intervals) that takes an array of intervals as input and returns a new array of merged, non-overlapping intervals. The input array intervals is a list of lists where each inner list has exactly two elements: the start and end of the interval. The function should return a list of lists with the same structure, representing the merged intervals. The order of the output intervals does not matter.

Sample Answer

Merge Intervals

Let's tackle the "Merge Intervals" problem. I've solved this a few times, so I have a good understanding of the nuances. I've been a Software Engineer at Google for 8 years now, working primarily on distributed systems, and have seen variations of this problem crop up in scheduling and resource allocation.

Problem Statement

Given a collection of intervals, merge all overlapping intervals.

1. Naive (Brute Force) Solution

The most straightforward approach is to iterate through all pairs of intervals and check if they overlap. If they do, merge them and repeat the process until no more merges are possible.

Algorithm:

  1. For each interval i in the list:
  2. For each interval j in the list (where j != i):
  3. Check if i and j overlap (i.e., i.start <= j.end and i.end >= j.start).
  4. If they overlap, merge i and j into a new interval and remove i and j from the list. Add the new interval to the list.
  5. Repeat steps 1-4 until no more merges occur.

Code (Python):

python def merge_intervals_naive(intervals): merged = False n = len(intervals) i = 0 while i < len(intervals): j = i + 1 while j < len(intervals): if intervals[i][0] <= intervals[j][1] and intervals[i][1] >= intervals[j][0]: # Overlap found, merge the intervals new_interval = [min(intervals[i][0], intervals[j][0]), max(intervals[i][1], intervals[j][1])] intervals.pop(j) # Remove j first as it is the higher index intervals.pop(i) intervals.insert(i, new_interval) merged = True break # Restart inner loop because the list has changed else: j += 1 if not merged: i += 1 # Only increment if no merge happened, otherwise stay in the same place and merge merged = False return intervals

Example Usage

intervals = [[1,3],[2,6],[8,10],[15,18]] print(merge_intervals_naive(intervals))

Big O Notation:

  • Time Complexity: O(n^2 * m), where n is the number of intervals, and m is the number of merge operations. Each iteration involves comparing all possible pairs, and merging can involve re-iterating the entire list. The 'm' factor is hard to pin down exactly, as it depends on the nature of the input (how often merges occur).
  • Space Complexity: O(1) - We are modifying the list in place. However, creating the new_interval could technically be O(1) space in each merge operation, so, considering memory allocation in general, O(m), where m is the max number of merge operations done in a single pass. More practically, it tends to be O(1).

2. Optimal Solution

The optimal solution involves sorting the intervals by their start times and then iterating through the sorted list, merging overlapping intervals as we go.

Algorithm:

  1. Sort the intervals by their start times.
  2. Initialize an empty list called merged_intervals.
  3. Add the first interval to merged_intervals.
  4. Iterate through the remaining intervals:
    • If the current interval overlaps with the last interval in merged_intervals, merge them.
    • Otherwise, add the current interval to merged_intervals.
  5. Return merged_intervals.

Code (Python):

python def merge_intervals_optimal(intervals): # Sort the intervals by start time intervals.sort(key=lambda x: x[0])

merged_intervals = []
if not intervals:
  return merged_intervals

merged_intervals.append(intervals[0])

for i in range(1, len(intervals)):
    current_interval = intervals[i]
    last_merged_interval = merged_intervals[-1]

    # Check for overlap
    if current_interval[0] <= last_merged_interval[1]:
        # Merge the intervals
        merged_intervals[-1] = [last_merged_interval[0], max(last_merged_interval[1], current_interval[1])]
    else:
        # No overlap, add the current interval to merged_intervals
        merged_intervals.append(current_interval)

return merged_intervals

Example Usage

intervals = [[1,3],[2,6],[8,10],[15,18]] print(merge_intervals_optimal(intervals))

Big O Notation:

  • Time Complexity: O(n log n) - This is dominated by the sorting step. The iteration through the sorted list takes O(n) time.
  • Space Complexity: O(n) in the worst case - If there are no overlapping intervals, merged_intervals will contain all the original intervals. In place sorting algorithms may reduce this to O(log n) or even O(1) in some implementations.

3. Edge Cases

  • Empty Input: An empty list of intervals should return an empty list.
  • Single Interval: A list with a single interval should return the same interval.
  • Non-Overlapping Intervals: A list of non-overlapping intervals should return the same list.
  • Overlapping Intervals: Correctly merging overlapping intervals, including cases where one interval is completely contained within another (e.g., [[1, 4], [2, 3]]).
  • Identical Intervals: Correctly handling identical intervals (e.g., [[1, 3], [1, 3]]).
  • Unsorted Input: The algorithm should work correctly regardless of whether the input intervals are initially sorted or not. The sort() ensures this.

4. Summary

The optimal solution provides a significant improvement in time complexity compared to the brute-force approach. The sorting step allows us to efficiently identify and merge overlapping intervals in a single pass.