Merge Overlapping Subintervals

Hero Image

DT

Dhaval Trivedi

Co-founder, Airtribe

Problem Overview

The problem involves merging overlapping intervals. You are given a collection of intervals represented as a list of pairs. Your task is to merge all overlapping intervals and return the result as a list of non-overlapping intervals that cover all the intervals in the input.

For example, given intervals [[1,3],[2,6],[8,10],[15,18]], the intervals [1,3] and [2,6] overlap with each other, so they should be merged into [1,6], resulting in the merged intervals [[1,6],[8,10],[15,18]].

Understanding the Problem

To successfully tackle the problem of merging intervals, it's crucial to comprehend the concept of overlapping intervals. Two intervals overlap if the end of one interval is equal to or greater than the start of a subsequent interval.

For instance, if we have two intervals [a, b] and [c, d], they overlap if b >= c. If they overlap, they should be merged into [a, max(b, d)].

Key DSA Concepts and Theory

Sorting

Sorting is a fundamental algorithmic concept that arranges data in a particular order. In this problem, sorting the intervals based on their start times simplifies subsequent processing. By doing this, overlapping intervals will be adjacent, making it easier to identify and merge them.

Merge Techniques

This problem exemplifies a classic "merge technique," frequently used in algorithms, such as merge sort. By comparing elements and merging properly, we effectively handle potentially overlapping data.

Solution Approach

The solution involves a systematic approach of sorting and then linearly merging intervals. Here's a step-by-step breakdown:

  1. Sort the Intervals: Begin by sorting the intervals based on their starting points.

  2. Iterate and Merge:

    • Initialize an empty list to store merged intervals.
    • Traverse each interval. Compare the current interval with the last interval in the merged list:
      • If they overlap, merge them.
      • If they don’t, add the current interval to the merged list.

C++ Code Example

#include <vector>
#include <algorithm>
#include <iostream>

using namespace std;

vector<vector<int>> mergeIntervals(vector<vector<int>>& intervals) {
    if (intervals.empty()) {
        return {};
    }
    
    // Sort intervals based on start times
    sort(intervals.begin(), intervals.end());
    
    vector<vector<int>> merged;
    merged.push_back(intervals[0]);
    
    for (int i = 1; i < intervals.size(); ++i) {
        // if the current interval overlaps with the last merged interval
        if (merged.back()[1] >= intervals[i][0]) {
            // merge them
            merged.back()[1] = max(merged.back()[1], intervals[i][1]);
        } else {
            // otherwise, add it to the list of merged intervals
            merged.push_back(intervals[i]);
        }
    }
    
    return merged;
}

int main() {
    vector<vector<int>> intervals = {{1,3},{2,6},{8,10},{15,18}};
    vector<vector<int>> result = mergeIntervals(intervals);
    
    for (auto &interval : result) {
        cout << "[" << interval[0] << "," << interval[1] << "] ";
    }
    return 0;
}

Java Code Example

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class MergeIntervals {
    public int[][] merge(int[][] intervals) {
        if (intervals.length <= 1)
            return intervals;

        // Sort intervals based on start times
        Arrays.sort(intervals, (a, b) -> Integer.compare(a[0], b[0]));

        List<int[]> merged = new ArrayList<>();
        int[] currentInterval = intervals[0];
        merged.add(currentInterval);

        for (int[] interval : intervals) {
            int currentBegin = currentInterval[0];
            int currentEnd = currentInterval[1];
            int nextBegin = interval[0];
            int nextEnd = interval[1];

            if (currentEnd >= nextBegin) {
                // Merge the overlapping intervals
                currentInterval[1] = Math.max(currentEnd, nextEnd);
            } else {
                // Move to the next interval
                currentInterval = interval;
                merged.add(currentInterval);
            }
        }

        return merged.toArray(new int[merged.size()][]);
    }

    public static void main(String[] args) {
        MergeIntervals mi = new MergeIntervals();
        int[][] intervals = {{1,3},{2,6},{8,10},{15,18}};
        int[][] result = mi.merge(intervals);
        for (int[] interval : result) {
            System.out.println(Arrays.toString(interval));
        }
    }
}

Time and Space Complexity Analysis

  • Time Complexity: The sorting step takes O(n log n), and the merging step takes O(n), leading to a total time complexity of O(n log n), where n is the number of intervals.
  • Space Complexity: The algorithm's space complexity is O(n) as it makes use of additional data structures to store merged intervals.

Common Mistakes to Avoid

  • Not sorting the intervals first. Sorting is crucial for the subsequent merge logic to work properly.
  • Overwriting the wrong parts of interval data during merging, which can happen if careful checks aren’t performed.

Similar Problems on LeetCode

  • Insert Interval (LeetCode #57)
  • Non-overlapping Intervals (LeetCode #435)
  • Interval List Intersections (LeetCode #986)

Additional Resources and References

By understanding and implementing the discussed approach, you can efficiently solve not just this problem but also gain a deeper understanding of interval problems and the merge technique.