027. Intersect Two Interval Lists
Problem
Return nonempty intersections of two sorted lists of pairwise disjoint half-open intervals [start,end), with start<end. Output fresh two-element lists in order. Touching endpoints alone have zero length and are not emitted. This half-open convention resolves the compressed source example's endpoint ambiguity.
Examples
Example 1
intersect_intervals([[0, 2], [5, 10]], [[1, 5], [8, 12]])
=> [[1, 2], [8, 10]]Endpoint-only contact at five is excluded.
Example 2
intersect_intervals([[1, 2]], [[2, 3]])
=> []Half-open intervals do not share the endpoint two.
Example 3
intersect_intervals([], [[1, 3]])
=> []One empty source prevents all intersections.
Reveal the worked solution and reasoning
Why this mechanism works
The overlap starts at the later start and ends at the earlier end. It exists only when start<end. After considering the two current intervals, the one ending first cannot intersect any later interval from the other list; that is the frontier that can be discarded safely.
State and boundaries
i and j are current interval indices. All intersections involving an interval before either frontier have been emitted.
Protocol
- Compute max of starts and min of ends.
- Emit if the resulting interval has positive length.
- Advance the interval that ends first.
- Stop when either source is exhausted.
Visualization
A: [0,2) [5,10)
B: [1,5) [8,12)
=> [1,2), [8,10); no [5,5)Python implementation
def intersect_intervals(a, b):
i = j = 0
output = []
while i < len(a) and j < len(b):
start = max(a[i][0], b[j][0])
end = min(a[i][1], b[j][1])
if start < end:
output.append([start, end])
if a[i][1] <= b[j][1]:
i += 1
else:
j += 1
return output
Worked execution
intersect_intervals([[0, 2], [5, 10]], [[1, 5], [8, 12]])
L2 intersect_intervals: i = j = 0 a=[[0, 2], [5, 10]]; b=[[1, 5], [8, 12]]; i=0; j=0 L3 intersect_intervals: output = [] a=[[0, 2], [5, 10]]; b=[[1, 5], [8, 12]]; i=0; j=0; output=[] L5 intersect_intervals: start = max(a[i][0], b[j][0]) a=[[0, 2], [5, 10]]; b=[[1, 5], [8, 12]]; i=0; j=0; output=[]; start=1 L6 intersect_intervals: end = min(a[i][1], b[j][1]) a=[[0, 2], [5, 10]]; b=[[1, 5], [8, 12]]; i=0; j=0; output=[]; start=1; end=2 L8 intersect_intervals: output.append([start, end]) a=[[0, 2], [5, 10]]; b=[[1, 5], [8, 12]]; i=0; j=0; output=[[1, 2]]; start=1; end=2 L10 intersect_intervals: i += 1 a=[[0, 2], [5, 10]]; b=[[1, 5], [8, 12]]; i=1; j=0; output=[[1, 2]]; start=1; end=2 L5 intersect_intervals: start = max(a[i][0], b[j][0]) a=[[0, 2], [5, 10]]; b=[[1, 5], [8, 12]]; i=1; j=0; output=[[1, 2]]; start=5; end=2 L6 intersect_intervals: end = min(a[i][1], b[j][1]) a=[[0, 2], [5, 10]]; b=[[1, 5], [8, 12]]; i=1; j=0; output=[[1, 2]]; start=5; end=5 ... 1 intermediate state changes omitted ... L5 intersect_intervals: start = max(a[i][0], b[j][0]) a=[[0, 2], [5, 10]]; b=[[1, 5], [8, 12]]; i=1; j=1; output=[[1, 2]]; start=8; end=5 L6 intersect_intervals: end = min(a[i][1], b[j][1]) a=[[0, 2], [5, 10]]; b=[[1, 5], [8, 12]]; i=1; j=1; output=[[1, 2]]; start=8; end=10 L8 intersect_intervals: output.append([start, end]) a=[[0, 2], [5, 10]]; b=[[1, 5], [8, 12]]; i=1; j=1; output=[[1, 2], [8, 10]]; start=8; end=10 L10 intersect_intervals: i += 1 a=[[0, 2], [5, 10]]; b=[[1, 5], [8, 12]]; i=2; j=1; output=[[1, 2], [8, 10]]; start=8; end=10
Result: [[1, 2], [8, 10]]. Endpoint-only contact at five is excluded.
intersect_intervals([[1, 2]], [[2, 3]])
L2 intersect_intervals: i = j = 0 a=[[1, 2]]; b=[[2, 3]]; i=0; j=0 L3 intersect_intervals: output = [] a=[[1, 2]]; b=[[2, 3]]; i=0; j=0; output=[] L5 intersect_intervals: start = max(a[i][0], b[j][0]) a=[[1, 2]]; b=[[2, 3]]; i=0; j=0; output=[]; start=2 L6 intersect_intervals: end = min(a[i][1], b[j][1]) a=[[1, 2]]; b=[[2, 3]]; i=0; j=0; output=[]; start=2; end=2 L10 intersect_intervals: i += 1 a=[[1, 2]]; b=[[2, 3]]; i=1; j=0; output=[]; start=2; end=2
Result: []. Half-open intervals do not share the endpoint two.
Why the solution is correct
The endpoint formula describes exactly the values shared by the current intervals. Disjoint sorted inputs ensure that the earlier-ending interval cannot overlap a later opposing interval after its current partner ends. Every possible positive-length overlap is therefore considered once.
Time and space
O(n+m) time and O(n+m) worst-case output storage; O(1) additional state.
Common mistakes
Using <= in the overlap test emits zero-length contacts. Advancing by the smaller start rather than the smaller end can discard an interval that still has future overlaps.
Reasoning card
State: i and j are current interval indices. All intersections involving an interval before either frontier have been emitted.
Invariant: A discarded interval cannot intersect any future interval.
Decision to rehearse: Advance the interval that ends first.
Finish: Stop when either source is exhausted
Transfer exercise
Convert the solution to closed intervals and include singleton intersections such as [5,5].