Topics › Greedy Algorithms › Medium/Hard
Insert Interval
LeetCode ↗ Java source ↗ Edit this article ↗
Jump to code:
Problem
You are given a list of closed intervals intervals[i] = [start_i, end_i] that is sorted by start and whose intervals are pairwise non-overlapping. You are also given one more interval, newInterval. Insert it so that the list stays sorted and non-overlapping, merging it with every interval it overlaps. Two closed intervals overlap when they share at least one point, so [1, 2] and [2, 3] merge into [1, 3]. Return the new list (a fresh array is fine).
Constraints: 0 ≤ n ≤ 10^4, 0 ≤ start_i ≤ end_i ≤ 10^5, and the same bounds for newInterval.
Example 1
intervals = [[1, 3], [6, 9]], newInterval = [2, 5]
answer = [[1, 5], [6, 9]]
[2, 5] overlaps [1, 3], so they merge into [1, 5]. It ends before [6, 9] starts, so that one is untouched.
Example 2
intervals = [[1, 2], [3, 5], [6, 7], [8, 10], [12, 16]], newInterval = [4, 8]
answer = [[1, 2], [3, 10], [12, 16]]
[4, 8] overlaps [3, 5], [6, 7] and [8, 10] (it touches 8), and the four together become [3, 10].
Approach 1: Brute force, append and merge everything
Intuition. If we did not know the list was sorted, we would solve the general “merge intervals” problem: throw the new interval into the pile, sort by start, and sweep once, either extending the last merged block or starting a new one. That is correct for any input, so it is a safe baseline. It ignores the fact that the list is already sorted and disjoint, which is why it is slower than needed.
Algorithm.
- Copy all intervals plus
newIntervalinto one array. - Sort by start.
- Sweep: if the current interval starts after the last block ends, start a new block; otherwise extend the last block’s end to the larger of the two ends.
static int[][] bruteForce(int[][] intervals, int[] newInterval) {
int n = intervals.length;
int[][] all = new int[n + 1][];
for (int i = 0; i < n; i++) all[i] = intervals[i].clone();
all[n] = newInterval.clone();
Arrays.sort(all, (x, y) -> Integer.compare(x[0], y[0]));
List<int[]> res = new ArrayList<>();
for (int[] cur : all) {
if (res.isEmpty() || cur[0] > res.get(res.size() - 1)[1]) {
res.add(cur); // gap: start a new block
} else {
int[] last = res.get(res.size() - 1);
last[1] = Math.max(last[1], cur[1]); // overlap: stretch the last block
}
}
return res.toArray(new int[0][]);
}
Complexity. Time O(n log n) for the sort; the sweep is O(n). Space O(n) for the copy and the output.
The copies (clone()) matter: the sweep mutates last[1], and without copying it would silently change the caller’s intervals.
Approach 2: Optimal, one pass in three phases
Intuition. Because the input is sorted and disjoint, both the starts and the ends increase along the list. Relative to newInterval = [start, end], every existing interval therefore falls into exactly one of three contiguous groups:
- Left group: intervals that end before
start(end_i < start). They cannot touch the new interval, and they form a prefix of the list because ends increase. - Overlap group: intervals that start at or before the current
end(start_i ≤ end). Each one is absorbed:startbecomes the smaller start andendthe larger end. We compare against the growingend, so the merged interval keeps swallowing as long as the next interval touches it. - Right group: everything after that starts beyond
end. Since starts increase, once one interval starts pastend, all later ones do too, and they are copied unchanged.
Between phases 2 and 3 we emit the merged interval, which lands exactly in its sorted position: everything before it ended earlier, everything after it starts later.
Algorithm.
i = 0. Whileintervals[i]ends beforestart, copy it and advance.- While
intervals[i]starts at or beforeend, setstart = min(start, s_i),end = max(end, e_i)and advance. - Append
[start, end]. - Copy the remaining intervals.
static int[][] optimal(int[][] intervals, int[] newInterval) {
List<int[]> res = new ArrayList<>();
int n = intervals.length, i = 0;
int start = newInterval[0], end = newInterval[1];
while (i < n && intervals[i][1] < start) { // entirely to the left: no contact
res.add(intervals[i].clone());
i++;
}
while (i < n && intervals[i][0] <= end) { // overlaps or touches the growing new interval
start = Math.min(start, intervals[i][0]);
end = Math.max(end, intervals[i][1]);
i++;
}
res.add(new int[]{start, end});
while (i < n) { // entirely to the right
res.add(intervals[i].clone());
i++;
}
return res.toArray(new int[0][]);
}
Dry run on example 2, newInterval = [4, 8]:
| i | interval | test | phase | start, end afterwards |
|---|---|---|---|---|
| 0 | [1, 2] | 2 < 4 | copy (left) | 4, 8 |
| 1 | [3, 5] | 3 ≤ 8 | absorb | 3, 8 |
| 2 | [6, 7] | 6 ≤ 8 | absorb | 3, 8 |
| 3 | [8, 10] | 8 ≤ 8 | absorb | 3, 10 |
| - | - | - | emit [3, 10] | - |
| 4 | [12, 16] | 12 > 10 | copy (right) | - |
Result [[1, 2], [3, 10], [12, 16]].
Complexity. Time O(n): each interval is looked at once. Space O(n) for the output; the extra working space is O(1).
You could binary-search the boundaries of the overlap group in O(log n), but building the output still copies n intervals, so the total stays O(n). Binary search only helps if you are allowed to modify a structure in place, such as a balanced tree of intervals.
Edge cases and pitfalls
- Strict vs non-strict comparisons. Phase 1 uses
end_i < startand phase 2 usesstart_i ≤ end. Together they make touching intervals merge. Making phase 1 non-strict turns[[1, 2], [5, 6]] + [2, 5]into[[1, 2], [2, 6]], and making phase 2 strict turns it into[[1, 5], [5, 6]]; the correct answer is[[1, 6]]. - Empty list. The answer is just
[newInterval]; both loops are skipped and phase 3 emits it. - New interval before or after everything. It is emitted first (
[[3, 5]] + [1, 2]) or last ([[1, 5]] + [6, 8]) without special-case code. - New interval in a gap.
[[1, 2], [8, 9]] + [4, 5]gives three intervals; phase 2 absorbs nothing. - New interval already covered.
[[1, 5]] + [2, 3]stays[[1, 5]]thanks to theminandmax. - New interval swallows everything.
[[2, 3], [4, 5]] + [1, 10]gives[[1, 10]]. - Do not mutate the input. Both methods copy intervals into the result instead of reusing the caller’s arrays.
Related problems
- Merge Intervals is the general version used as the brute force here.
- Non-overlapping Intervals asks how many intervals to remove instead of merging them.
- Merge Overlapping Subintervals is the same merge problem from the arrays section.
- Meeting Rooms checks whether any two intervals overlap at all.
- Meeting Rooms II counts how many intervals overlap at the busiest moment.