Facebook Pixel

57. Insert Interval

Problem Description

You are given an array of non-overlapping intervals where each interval is represented as [start, end]. The intervals are sorted in ascending order by their start times. You also receive a new interval newInterval = [start, end].

Your task is to insert this new interval into the existing array of intervals while maintaining two conditions:

  1. The resulting array should remain sorted by start times
  2. There should be no overlapping intervals (if inserting causes overlaps, you need to merge them)

The merging rule is: when two intervals overlap (meaning one interval's end time is greater than or equal to another interval's start time), they should be combined into a single interval that spans from the minimum start time to the maximum end time.

For example:

  • If you have intervals [[1,3], [6,9]] and need to insert [2,5], the result would be [[1,5], [6,9]] because [1,3] and [2,5] overlap and merge into [1,5]
  • If you have intervals [[1,2], [3,5], [6,7], [8,10], [12,16]] and need to insert [4,8], the result would be [[1,2], [3,10], [12,16]] because [3,5], [4,8], [6,7], and [8,10] all overlap and merge into [3,10]

The solution approach adds the new interval to the existing list and then performs a standard interval merging operation: sort all intervals by start time, then iterate through them, merging any that overlap by extending the end time of the previous interval when needed.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

When we need to insert a new interval into a sorted list of non-overlapping intervals, we face two main challenges: finding where the new interval fits and handling any overlaps it might create.

The key insight is that this problem is essentially a variant of the classic "merge intervals" problem. Instead of trying to find the exact position to insert and then handle the merging logic separately, we can simplify the approach by treating the new interval as just another interval in the collection.

Think about it this way: if we already had all intervals (including the new one) in a list, we would simply sort them and merge any overlapping ones. The fact that one interval is "new" doesn't really matter for the final result - what matters is that all intervals are properly merged.

This leads us to a straightforward two-step approach:

  1. Add the new interval to the existing list
  2. Apply the standard interval merging algorithm

The merging algorithm itself follows a natural pattern: after sorting by start times, we traverse the intervals and for each one, we check if it overlaps with the last interval in our result. If it does (meaning the last interval's end time >= current interval's start time), we extend the last interval. If not, we add the current interval as a new separate interval.

This approach elegantly handles all cases:

  • If the new interval doesn't overlap with any existing ones, it simply gets placed in its correct sorted position
  • If it overlaps with one interval, they merge into one
  • If it spans multiple intervals, they all collapse into a single merged interval

The beauty of this solution is its simplicity - rather than writing complex logic to handle insertion and merging separately, we reduce the problem to a well-known pattern that's easier to implement and understand.

Solution Approach

The implementation follows the sorting and interval merging strategy outlined in the reference approach. Let's walk through the code step by step:

Step 1: Add the new interval to the existing list

intervals.append(newInterval)

This simply adds newInterval to the intervals list, treating it like any other interval that needs to be processed.

Step 2: Apply the merge function The core logic is in the merge helper function that performs standard interval merging:

def merge(intervals: List[List[int]]) -> List[List[int]]:
    intervals.sort()
    ans = [intervals[0]]
    for s, e in intervals[1:]:
        if ans[-1][1] < s:
            ans.append([s, e])
        else:
            ans[-1][1] = max(ans[-1][1], e)
    return ans

Let's break down the merging algorithm:

  1. Sort the intervals: intervals.sort() sorts all intervals by their start time (Python sorts lists of lists by the first element by default).

  2. Initialize the result: ans = [intervals[0]] starts with the first interval in our result list.

  3. Iterate through remaining intervals: For each interval [s, e] starting from the second one:

    • Check for overlap: if ans[-1][1] < s
      • If the last interval's end time is less than the current interval's start time, there's no overlap
      • In this case, add the current interval as a new separate interval: ans.append([s, e])
    • Merge if overlapping: else
      • If there is overlap, extend the last interval's end time to cover both intervals
      • Use max(ans[-1][1], e) to ensure we take the furthest end point

Example walkthrough: If we have intervals = [[1,3], [6,9]] and newInterval = [2,5]:

  1. After appending: [[1,3], [6,9], [2,5]]
  2. After sorting: [[1,3], [2,5], [6,9]]
  3. Initialize ans = [[1,3]]
  4. Process [2,5]: Since 3 >= 2 (overlap), merge to get ans = [[1,5]]
  5. Process [6,9]: Since 5 < 6 (no overlap), append to get ans = [[1,5], [6,9]]

The time complexity is O(n log n) due to sorting, where n is the total number of intervals. The space complexity is O(n) for storing the result array.

Ready to land your dream job?

Unlock your dream job with a 3-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a concrete example to illustrate the solution approach.

Given:

  • Existing intervals: [[1,2], [3,5], [6,7], [8,10], [12,16]]
  • New interval to insert: [4,8]

Step 1: Add the new interval to the list

intervals = [[1,2], [3,5], [6,7], [8,10], [12,16], [4,8]]

Step 2: Sort all intervals by start time

After sorting: [[1,2], [3,5], [4,8], [6,7], [8,10], [12,16]]

Step 3: Merge overlapping intervals

Initialize result with first interval:

ans = [[1,2]]

Process each remaining interval:

  1. Process [3,5]:

    • Check: Is 2 (last interval's end) < 3 (current start)? Yes!
    • No overlap → Append [3,5]
    • ans = [[1,2], [3,5]]
  2. Process [4,8]:

    • Check: Is 5 (last interval's end) < 4 (current start)? No! (5 ≥ 4)
    • Overlap detected → Merge by extending end time
    • New end = max(5, 8) = 8
    • ans = [[1,2], [3,8]]
  3. Process [6,7]:

    • Check: Is 8 (last interval's end) < 6 (current start)? No! (8 ≥ 6)
    • Overlap detected → Merge by extending end time
    • New end = max(8, 7) = 8 (no change)
    • ans = [[1,2], [3,8]]
  4. Process [8,10]:

    • Check: Is 8 (last interval's end) < 8 (current start)? No! (8 ≥ 8)
    • Overlap detected → Merge by extending end time
    • New end = max(8, 10) = 10
    • ans = [[1,2], [3,10]]
  5. Process [12,16]:

    • Check: Is 10 (last interval's end) < 12 (current start)? Yes!
    • No overlap → Append [12,16]
    • ans = [[1,2], [3,10], [12,16]]

Final Result: [[1,2], [3,10], [12,16]]

The new interval [4,8] caused four originally separate intervals [3,5], [4,8], [6,7], [8,10] to merge into one consolidated interval [3,10].

Solution Implementation

1class Solution:
2    def insert(
3        self, intervals: List[List[int]], newInterval: List[int]
4    ) -> List[List[int]]:
5        """
6        Insert a new interval into a list of non-overlapping intervals and merge if necessary.
7      
8        Args:
9            intervals: List of non-overlapping intervals sorted by start time
10            newInterval: The interval to be inserted
11          
12        Returns:
13            List of intervals after insertion with all overlapping intervals merged
14        """
15      
16        def merge_intervals(interval_list: List[List[int]]) -> List[List[int]]:
17            """
18            Merge overlapping intervals in a list.
19          
20            Args:
21                interval_list: List of intervals that may have overlaps
22              
23            Returns:
24                List of merged non-overlapping intervals
25            """
26            # Sort intervals by their start time
27            interval_list.sort()
28          
29            # Initialize result with the first interval
30            merged_result = [interval_list[0]]
31          
32            # Iterate through remaining intervals
33            for start, end in interval_list[1:]:
34                # Check if current interval overlaps with the last merged interval
35                if merged_result[-1][1] < start:
36                    # No overlap - add as a new interval
37                    merged_result.append([start, end])
38                else:
39                    # Overlap exists - extend the end time of the last merged interval
40                    merged_result[-1][1] = max(merged_result[-1][1], end)
41          
42            return merged_result
43      
44        # Add the new interval to the existing intervals
45        intervals.append(newInterval)
46      
47        # Merge all intervals and return the result
48        return merge_intervals(intervals)
49
1class Solution {
2    /**
3     * Inserts a new interval into a list of non-overlapping intervals and merges if necessary.
4     * @param intervals - array of non-overlapping intervals
5     * @param newInterval - the interval to be inserted
6     * @return merged intervals after insertion
7     */
8    public int[][] insert(int[][] intervals, int[] newInterval) {
9        // Create a new array with space for the additional interval
10        int[][] combinedIntervals = new int[intervals.length + 1][2];
11      
12        // Copy all existing intervals to the new array
13        for (int i = 0; i < intervals.length; i++) {
14            combinedIntervals[i] = intervals[i];
15        }
16      
17        // Add the new interval at the end
18        combinedIntervals[intervals.length] = newInterval;
19      
20        // Merge all intervals and return the result
21        return merge(combinedIntervals);
22    }
23
24    /**
25     * Merges overlapping intervals in the given array.
26     * @param intervals - array of intervals that may contain overlaps
27     * @return array of merged non-overlapping intervals
28     */
29    private int[][] merge(int[][] intervals) {
30        // Sort intervals by their start time
31        Arrays.sort(intervals, (a, b) -> a[0] - b[0]);
32      
33        // List to store merged intervals
34        List<int[]> mergedIntervals = new ArrayList<>();
35      
36        // Add the first interval as the starting point
37        mergedIntervals.add(intervals[0]);
38      
39        // Iterate through remaining intervals
40        for (int i = 1; i < intervals.length; i++) {
41            int currentStart = intervals[i][0];
42            int currentEnd = intervals[i][1];
43          
44            // Get the last interval in the merged list
45            int[] lastMergedInterval = mergedIntervals.get(mergedIntervals.size() - 1);
46          
47            // Check if current interval overlaps with the last merged interval
48            if (lastMergedInterval[1] < currentStart) {
49                // No overlap, add current interval as a new entry
50                mergedIntervals.add(intervals[i]);
51            } else {
52                // Overlap exists, merge by extending the end time of the last interval
53                lastMergedInterval[1] = Math.max(lastMergedInterval[1], currentEnd);
54            }
55        }
56      
57        // Convert list to array and return
58        return mergedIntervals.toArray(new int[mergedIntervals.size()][]);
59    }
60}
61
1class Solution {
2public:
3    /**
4     * Insert a new interval into a list of non-overlapping intervals and merge if necessary
5     * @param intervals: list of non-overlapping intervals sorted by start time
6     * @param newInterval: the interval to be inserted
7     * @return: list of non-overlapping intervals after insertion
8     */
9    vector<vector<int>> insert(vector<vector<int>>& intervals, vector<int>& newInterval) {
10        // Add the new interval to the existing intervals
11        intervals.emplace_back(newInterval);
12      
13        // Merge all overlapping intervals
14        return merge(intervals);
15    }
16
17private:
18    /**
19     * Merge overlapping intervals in a list
20     * @param intervals: list of intervals (may contain overlapping intervals)
21     * @return: list of non-overlapping intervals after merging
22     */
23    vector<vector<int>> merge(vector<vector<int>>& intervals) {
24        // Sort intervals by their start time
25        sort(intervals.begin(), intervals.end());
26      
27        // Initialize result vector with the first interval
28        vector<vector<int>> mergedIntervals;
29        mergedIntervals.emplace_back(intervals[0]);
30      
31        // Iterate through remaining intervals
32        for (int i = 1; i < intervals.size(); ++i) {
33            // Check if current interval overlaps with the last merged interval
34            if (mergedIntervals.back()[1] < intervals[i][0]) {
35                // No overlap - add current interval as a new element
36                mergedIntervals.emplace_back(intervals[i]);
37            } else {
38                // Overlap detected - extend the end of the last merged interval
39                mergedIntervals.back()[1] = max(mergedIntervals.back()[1], intervals[i][1]);
40            }
41        }
42      
43        return mergedIntervals;
44    }
45};
46
1/**
2 * Inserts a new interval into a list of non-overlapping intervals and merges any overlapping intervals
3 * @param intervals - Array of non-overlapping intervals where intervals[i] = [start_i, end_i]
4 * @param newInterval - The new interval to be inserted [start, end]
5 * @returns The modified list of intervals after insertion and merging
6 */
7function insert(intervals: number[][], newInterval: number[]): number[][] {
8    /**
9     * Merges overlapping intervals in a given array
10     * @param intervals - Array of intervals to be merged
11     * @returns Array of merged non-overlapping intervals
12     */
13    const mergeIntervals = (intervals: number[][]): number[][] => {
14        // Sort intervals by their start time in ascending order
15        intervals.sort((a, b) => a[0] - b[0]);
16      
17        // Initialize result array with the first interval
18        const mergedResult: number[][] = [intervals[0]];
19      
20        // Iterate through remaining intervals
21        for (let i = 1; i < intervals.length; i++) {
22            const lastMergedInterval = mergedResult[mergedResult.length - 1];
23            const currentInterval = intervals[i];
24          
25            // Check if current interval overlaps with the last merged interval
26            if (lastMergedInterval[1] < currentInterval[0]) {
27                // No overlap: add current interval as a new separate interval
28                mergedResult.push(currentInterval);
29            } else {
30                // Overlap detected: merge by extending the end time of the last interval
31                lastMergedInterval[1] = Math.max(lastMergedInterval[1], currentInterval[1]);
32            }
33        }
34      
35        return mergedResult;
36    };
37  
38    // Add the new interval to the existing intervals
39    intervals.push(newInterval);
40  
41    // Merge all intervals and return the result
42    return mergeIntervals(intervals);
43}
44

Time and Space Complexity

Time Complexity: O(n × log n)

The time complexity is dominated by the sorting operation in the merge function. After appending newInterval to intervals, we have n total intervals (where n is the original number of intervals plus one). The sort() operation takes O(n × log n) time. The subsequent iteration through the sorted intervals takes O(n) time, but this is overshadowed by the sorting complexity.

Space Complexity: O(n)

The space complexity comes from several sources:

  • The ans list in the merge function, which in the worst case stores all n intervals when no merging occurs, requiring O(n) space
  • The sorting operation may use O(log n) to O(n) additional space depending on the implementation (Python's Timsort uses up to O(n) space in worst case)
  • The overall space complexity is therefore O(n)

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Modifying the Input List In-Place

The most common pitfall in this solution is that intervals.append(newInterval) modifies the original input list. This can cause unexpected side effects if the caller still needs the original intervals list unchanged.

Problem Example:

original_intervals = [[1,3], [6,9]]
new_interval = [2,5]
solution = Solution()
result = solution.insert(original_intervals, new_interval)
print(original_intervals)  # [[1,3], [6,9], [2,5]] - Original list is modified!

Solution: Create a copy of the intervals list before modification:

def insert(self, intervals: List[List[int]], newInterval: List[int]) -> List[List[int]]:
    # Create a new list to avoid modifying the input
    all_intervals = intervals + [newInterval]
    return self.merge_intervals(all_intervals)

2. Inefficient Approach for Already Sorted Intervals

The current solution sorts all intervals even though the input intervals are already sorted. This adds unnecessary O(n log n) complexity when we could achieve O(n) by taking advantage of the pre-sorted nature.

Optimized Solution:

def insert(self, intervals: List[List[int]], newInterval: List[int]) -> List[List[int]]:
    result = []
    i = 0
    n = len(intervals)
  
    # Add all intervals that end before newInterval starts
    while i < n and intervals[i][1] < newInterval[0]:
        result.append(intervals[i])
        i += 1
  
    # Merge all overlapping intervals with newInterval
    while i < n and intervals[i][0] <= newInterval[1]:
        newInterval[0] = min(newInterval[0], intervals[i][0])
        newInterval[1] = max(newInterval[1], intervals[i][1])
        i += 1
    result.append(newInterval)
  
    # Add all remaining intervals
    while i < n:
        result.append(intervals[i])
        i += 1
  
    return result

3. Edge Case: Empty Intervals List

While the current solution handles empty lists correctly, it's worth noting that the merge function would fail if called directly with an empty list due to ans = [intervals[0]].

Solution: Add an edge case check:

def merge_intervals(interval_list: List[List[int]]) -> List[List[int]]:
    if not interval_list:
        return []
  
    interval_list.sort()
    merged_result = [interval_list[0]]
    # ... rest of the code

4. Deep Copy Issues with Nested Lists

When creating the result list, using shallow references can lead to unintended modifications:

# Problematic:
ans.append([s, e])  # If [s, e] is a reference to an existing interval

# Better:
ans.append([s, e])  # Creates a new list (which the code already does correctly)
Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

A heap is a ...?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More