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:
- The resulting array should remain sorted by start times
- 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.
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:
- Add the new interval to the existing list
- 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:
-
Sort the intervals:
intervals.sort()
sorts all intervals by their start time (Python sorts lists of lists by the first element by default). -
Initialize the result:
ans = [intervals[0]]
starts with the first interval in our result list. -
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
- Check for overlap:
Example walkthrough:
If we have intervals = [[1,3], [6,9]]
and newInterval = [2,5]
:
- After appending:
[[1,3], [6,9], [2,5]]
- After sorting:
[[1,3], [2,5], [6,9]]
- Initialize
ans = [[1,3]]
- Process
[2,5]
: Since3 >= 2
(overlap), merge to getans = [[1,5]]
- Process
[6,9]
: Since5 < 6
(no overlap), append to getans = [[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 EvaluatorExample 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:
-
Process [3,5]:
- Check: Is
2
(last interval's end)< 3
(current start)? Yes! - No overlap → Append
[3,5]
ans = [[1,2], [3,5]]
- Check: Is
-
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]]
- Check: Is
-
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]]
- Check: Is
-
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]]
- Check: Is
-
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]]
- Check: Is
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 themerge
function, which in the worst case stores alln
intervals when no merging occurs, requiringO(n)
space - The sorting operation may use
O(log n)
toO(n)
additional space depending on the implementation (Python's Timsort uses up toO(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)
A heap is a ...?
Recommended Readings
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!