You are given an array of non-overlapping intervals, intervals
, where each interval is represented as a pair [start, end]
and is sorted in ascending order by its start value. You are also given another interval, newInterval
, which you need to insert into intervals
so that the resulting array is still sorted and contains no overlapping intervals.
If newInterval
overlaps with existing intervals, you must merge all overlapping intervals into a single interval. The output should be the updated list of intervals.
[start, end]
with start <= end
.intervals
overlap before insertion.[start, end]
, sorted by their start times.
At first glance, you might think of inserting newInterval
into the correct position and then scanning the entire array to merge any overlapping intervals. However, since intervals
is already sorted and non-overlapping, we can take advantage of this structure to efficiently insert and merge in a single pass.
The key realization is that there are three types of intervals in relation to newInterval
:
newInterval
starts (no overlap, appear before).newInterval
(need to merge).newInterval
ends (no overlap, appear after).Here is a step-by-step breakdown of the algorithm:
intervals
:
newInterval
starts: Add it directly to the result list. There is no overlap.
newInterval
ends: Add newInterval
to the result (if not already added), then add all remaining intervals as they are. We can stop merging at this point.
newInterval
: Merge them by updating newInterval
to cover the union of both intervals. Do not add anything to the result yet.
newInterval
to the result. This covers the case where newInterval
extends beyond all existing intervals.
This approach ensures we only traverse the list once, and only merge when necessary, making it efficient and easy to understand.
Let's walk through an example:
Input:
intervals = [[1,3],[6,9]]
newInterval = [2,5]
[1,3]
. Since 3 >= 2
(overlap), we merge [1,3]
and [2,5]
to get [1,5]
.
[6,9]
. Since 6 > 5
(no overlap), we add the merged interval [1,5]
to the result, then add [6,9]
.
Output: [[1,5],[6,9]]
The process efficiently merges only where necessary and preserves the sorted, non-overlapping property.
Brute-force approach: Inserting the interval, sorting, and then merging all intervals would take O(n \log n)
time due to sorting, and O(n)
space.
Optimized approach (our solution): We make a single pass through the intervals, so the time complexity is O(n)
, where n
is the number of intervals. Space complexity is also O(n)
due to the result list.
The Insert Interval problem leverages the sorted, non-overlapping nature of the input to allow a one-pass, linear-time solution. By categorizing intervals as before, overlapping, or after the new interval, we merge only where necessary and build the result efficiently. This approach is both elegant and practical, illustrating the power of using input constraints to simplify complex problems.
class Solution:
def insert(self, intervals, newInterval):
res = []
i = 0
n = len(intervals)
# Add all intervals before newInterval
while i < n and intervals[i][1] < newInterval[0]:
res.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
res.append(newInterval)
# Add all intervals after newInterval
while i < n:
res.append(intervals[i])
i += 1
return res
class Solution {
public:
vector<vector<int>> insert(vector<vector<int>>& intervals, vector<int>& newInterval) {
vector<vector<int>> res;
int i = 0, n = intervals.size();
// Add all intervals before newInterval
while (i < n && intervals[i][1] < newInterval[0]) {
res.push_back(intervals[i]);
i++;
}
// Merge all overlapping intervals with newInterval
while (i < n && intervals[i][0] <= newInterval[1]) {
newInterval[0] = min(newInterval[0], intervals[i][0]);
newInterval[1] = max(newInterval[1], intervals[i][1]);
i++;
}
res.push_back(newInterval);
// Add all intervals after newInterval
while (i < n) {
res.push_back(intervals[i]);
i++;
}
return res;
}
};
class Solution {
public int[][] insert(int[][] intervals, int[] newInterval) {
List<int[]> res = new ArrayList<>();
int i = 0, n = intervals.length;
// Add all intervals before newInterval
while (i < n && intervals[i][1] < newInterval[0]) {
res.add(intervals[i]);
i++;
}
// Merge all overlapping intervals with newInterval
while (i < n && intervals[i][0] <= newInterval[1]) {
newInterval[0] = Math.min(newInterval[0], intervals[i][0]);
newInterval[1] = Math.max(newInterval[1], intervals[i][1]);
i++;
}
res.add(newInterval);
// Add all intervals after newInterval
while (i < n) {
res.add(intervals[i]);
i++;
}
return res.toArray(new int[res.size()][]);
}
}
var insert = function(intervals, newInterval) {
const res = [];
let i = 0, n = intervals.length;
// Add all intervals before newInterval
while (i < n && intervals[i][1] < newInterval[0]) {
res.push(intervals[i]);
i++;
}
// Merge all overlapping intervals with newInterval
while (i < n && intervals[i][0] <= newInterval[1]) {
newInterval[0] = Math.min(newInterval[0], intervals[i][0]);
newInterval[1] = Math.max(newInterval[1], intervals[i][1]);
i++;
}
res.push(newInterval);
// Add all intervals after newInterval
while (i < n) {
res.push(intervals[i]);
i++;
}
return res;
};