The problem
You get two lists of closed intervals, A and B. Each list is sorted and its intervals do not overlap each other. Return every interval where the two lists overlap.
A = [[0, 2], [5, 10], [13, 23], [24, 25]]
B = [[1, 5], [8, 12], [15, 24], [25, 26]]
Output: [[1, 2], [5, 5], [8, 10], [15, 23], [24, 24], [25, 25]]For example [0, 2] and [1, 5] share [1, 2], and [5, 10] and [1, 5] share just the point [5, 5].
How to think about it
Start with brute force. Compare every interval in A with every interval in B and keep the non-empty overlaps. That is O(m·n), and it ignores that both lists are sorted.
How to intersect two intervals. The overlap of [a1, a2] and [b1, b2] starts at the later start and ends at the earlier end: [max(a1, b1), min(a2, b2)]. It is real only if lo <= hi.
The key insight: advance the one that ends first. Look at the current pair A[i], B[j]. Whichever ends earlier cannot overlap anything further in the other list, because every later interval there starts even later. So it is finished; move its pointer. The other interval might still overlap the next one, so keep it. Each step moves one pointer, so the walk is linear.
Which pattern is this? Intervals with two pointers, like merging two sorted lists. The tell is “two sorted interval lists”.
Approach
- Set
i = j = 0andres = []. - While both pointers are in range, compute
lo = max(A[i][0], B[j][0])andhi = min(A[i][1], B[j][1]). - If
lo <= hi, append[lo, hi]. - If
A[i]ends beforeB[j], advancei; otherwise advancej. - Return
res.
Solution
def interval_intersection(A, B):
i = j = 0; res = []
while i < len(A) and j < len(B):
lo = max(A[i][0], B[j][0]); hi = min(A[i][1], B[j][1]) # candidate overlap
if lo <= hi: res.append([lo, hi]) # non-empty: keep it
if A[i][1] < B[j][1]: i += 1 # A[i] ends first: done with it
else: j += 1
return res
# interval_intersection([[0,2],[5,10],[13,23],[24,25]], [[1,5],[8,12],[15,24],[25,26]])
# -> [[1,2],[5,5],[8,10],[15,23],[24,24],[25,25]]Dry run
A[i] |
B[j] |
lo, hi |
Added | Advance |
|---|---|---|---|---|
| [0, 2] | [1, 5] | 1, 2 | [1, 2] | i |
| [5, 10] | [1, 5] | 5, 5 | [5, 5] | j |
| [5, 10] | [8, 12] | 8, 10 | [8, 10] | i |
| [13, 23] | [8, 12] | 13, 12 | none | j |
| [13, 23] | [15, 24] | 15, 23 | [15, 23] | i |
| [24, 25] | [15, 24] | 24, 24 | [24, 24] | j |
| [24, 25] | [25, 26] | 25, 25 | [25, 25] | i |
i runs off the end of A, so the loop stops with six intersections.
Complexity
- Time: O(m + n). Every iteration advances one pointer.
- Space: O(1) extra. Only the output list grows (it has at most m + n entries).
Edge cases and pitfalls
- Either list empty returns
[]. - Single-point overlaps like
[5, 5]count because intervals are closed; uselo <= hi, notlo < hi. - Equal ends: when
A[i][1] == B[j][1]the code advancesj. Either choice is fine, since both are finished and the other will be advanced next round. - Advancing by start instead of end skips overlaps: an interval that starts early but ends late can still overlap the next interval in the other list.
Related problems
- Merge Intervals (LeetCode 56): union of intervals instead of intersection.
- Merge Two Sorted Lists (LeetCode 21): the same “advance the smaller one” two-pointer walk.
- Employee Free Time (LeetCode 759): working across several people’s interval lists at once.
- 1.Meeting Rooms (LeetCode 252)
- 2.Merge Intervals (LeetCode 56)
- 3.Insert Interval (LeetCode 57)
- 4.Non-overlapping Intervals (LeetCode 435)
- 5.Meeting Rooms II (LeetCode 253)
- 6.Interval List Intersections (LeetCode 986)
- 7.Minimum Number of Arrows to Burst Balloons (LeetCode 452)
- 8.Car Pooling (LeetCode 1094)
- 9.My Calendar I (LeetCode 729)
- 10.Employee Free Time (LeetCode 759)