Insert Interval (LC 57)
On this page
Pattern: Merge Intervals
Difficulty: Medium
Key Concept: Insert a new interval into a sorted, non-overlapping list by scanning in three phases—before, merge, after—without full resorting.
Problem Statement
You are given an array of intervals where intervals[i] = [start_i, end_i] are sorted by start time and do not overlap. You are also given newInterval = [start, end].
Insert newInterval into intervals such that the result is still sorted and non-overlapping, merging newInterval with any existing intervals that overlap it.
Input: intervals — int[][] sorted, disjoint; newInterval — int[] of length 2.
Output: int[][] — merged, sorted, non-overlapping intervals covering the union of all intervals plus newInterval.
1. Algorithm & Pseudocode
Brute force
- Copy all intervals plus
newIntervalinto a list. - Sort the list by start time (e.g.
Arrays.sorton a 2D array or sort a list ofint[]). - Merge adjacent overlapping intervals in one linear scan (same idea as LC 56).
Optimal (three-phase linear scan)
- Let
newStart = newInterval[0],newEnd = newInterval[1],i = 0,n = intervals.length,resultempty. - Phase A — strictly before: While
i < nandintervals[i][1] < newStart, appendintervals[i]toresult,i++. - Phase B — merge: While
i < nandintervals[i][0] <= newEnd, setnewStart = min(newStart, intervals[i][0]),newEnd = max(newEnd, intervals[i][1]),i++. Append[newStart, newEnd]once. - Phase C — strictly after: While
i < n, appendintervals[i],i++. - Convert
resulttoint[][](e.g.result.toArray(new int[0][])).
2. Step-by-Step Analysis (Beginner-Friendly)
- Why not only sort? Sorting works but costs O(n log n). The input is already sorted except for one new interval; we can exploit order with a single O(n) pass.
- Phase A: Any interval that ends before
newStartcannot overlap the new interval, so we copy it unchanged. - Phase B: If an interval’s start is ≤ the current merged end, it overlaps the growing block; we expand
newStart/newEndto the union. We stop when the next interval starts afternewEnd. - Phase C: Everything left starts after the merged block, so no further merging with the inserted interval—append as-is.
- Why
intervals[i][0] <= newEnd? Overlap (touching counts as mergeable in this problem) means the next interval is not entirely to the right of the merged region.
3. The Dry Run
Input: intervals = [[1,3],[6,9]], newInterval = [2,5]
So initially newStart = 2, newEnd = 5, i = 0, result = [].
| Step | Phase | i |
Action | newStart |
newEnd |
result (after step) |
|---|---|---|---|---|---|---|
| 1 | A | 0 | [1,3]: 3 < 2? No — skip A for this interval |
2 | 5 | [] |
| 2 | B | 0 | [1,3]: 1 <= 5? Yes — merge: newStart = min(2,1)=1, newEnd = max(5,3)=5, i → 1 |
1 | 5 | [] |
| 3 | B | 1 | [6,9]: 6 <= 5? No — exit B |
1 | 5 | [] |
| 4 | B | — | Append merged [1,5] |
1 | 5 | [[1,5]] |
| 5 | C | 1 | Append [6,9], i → 2 |
1 | 5 | [[1,5],[6,9]] |
| 6 | — | 2 | i == n, done |
— | — | [[1,5],[6,9]] |
Output: [[1,5],[6,9]].
4. Java Solution
Brute Force
import java.util.*;
class Solution {
public int[][] insert(int[][] intervals, int[] newInterval) {
List<int[]> list = new ArrayList<>();
for (int[] iv : intervals) {
list.add(iv);
}
list.add(newInterval);
list.sort(Comparator.comparingInt(a -> a[0]));
List<int[]> merged = new ArrayList<>();
for (int[] cur : list) {
if (merged.isEmpty() || merged.get(merged.size() - 1)[1] < cur[0]) {
merged.add(cur);
} else {
int[] last = merged.get(merged.size() - 1);
last[1] = Math.max(last[1], cur[1]);
}
}
return merged.toArray(new int[0][]);
}
}
Time: O(n log n) (sort dominates). Space: O(n) for the list and merge buffer.
Optimal
import java.util.*;
class Solution {
public int[][] insert(int[][] intervals, int[] newInterval) {
List<int[]> res = new ArrayList<>();
int i = 0;
int n = intervals.length;
int newStart = newInterval[0];
int newEnd = newInterval[1];
while (i < n && intervals[i][1] < newStart) {
res.add(intervals[i]);
i++;
}
while (i < n && intervals[i][0] <= newEnd) {
newStart = Math.min(newStart, intervals[i][0]);
newEnd = Math.max(newEnd, intervals[i][1]);
i++;
}
res.add(new int[] { newStart, newEnd });
while (i < n) {
res.add(intervals[i]);
i++;
}
return res.toArray(new int[0][]);
}
}
Time: O(n). Space: O(n) for the output list (excluding output, only O(1) extra).
5. The “Java vs. Others” Edge
List.toArray(new int[0][]): ConvertsArrayList<int[]>toint[][]. Thenew int[0][]trick lets the JVM allocate an array of the correct runtime size; sizing manually is error-prone.Integer.compare(a, b)(orComparator.comparingInt(a -> a[0])): Avoids overflow pitfalls ofa - bwhen values are nearInteger.MIN_VALUE/MAX_VALUE.ArrayListgrowth: Backed by an array; when full, capacity grows (typically about 1.5×), so amortized append is O(1)—good to mention in interviews when discussing space spikes during building.- C++/Python contrast: C++ might use
vector<vector<int>>andpush_back; Python lists are dynamic but returninglist[list[int]]vs LeetCode’sList[List[int]]is similar. Java’s explicitint[][]+ArrayList<int[]>split is a common LeetCode pattern.
6. Complexity Summary
| Approach | Time | Space | Notes |
|---|---|---|---|
| Brute Force | O(n log n) | O(n) | Sort all, then merge like LC 56. |
| Optimal | O(n) | O(n) output, O(1) auxiliary | Single pass using sorted input + three phases. |