Prerequisites
Before attempting this problem, you should be comfortable with:
- Intervals - Understanding interval representation and overlap detection logic
- Two Pointers - Using two indices to traverse two sorted lists simultaneously
- Line Sweep Algorithm - Processing events at specific points to track active intervals
- Sorted Arrays - Leveraging sorted order to optimize comparison-based algorithms
1. Brute Force
Intuition
The simplest way to find intersections is to check every interval in the first list against every interval in the second list. Two intervals overlap when one starts before the other ends and vice versa. If they do overlap, the intersection spans from the later start point to the earlier end point. This approach is straightforward but involves redundant comparisons since we ignore the fact that both lists are already sorted.
Algorithm
- Initialize an empty result list.
- For each interval
[startA, endA]in the first list:- For each interval
[startB, endB]in the second list:- Check if they overlap by verifying that one interval's start falls within the other.
- If they overlap, compute the intersection as
[max(startA, startB), min(endA, endB)]and add it to the result.
- For each interval
- Return the result list.
class Solution:
def intervalIntersection(self, firstList: List[List[int]], secondList: List[List[int]]) -> List[List[int]]:
res = []
for i in range(len(firstList)):
startA, endA = firstList[i][0], firstList[i][1]
for j in range(len(secondList)):
startB, endB = secondList[j][0], secondList[j][1]
if (startA <= startB <= endA) or (startB <= startA <= endB):
res.append([max(startA, startB), min(endA, endB)])
return resTime & Space Complexity
- Time complexity:
- Space complexity:
- extra space.
- for the output list.
Where and are the sizes of the arrays and , respectively.
2. Line Sweep
Intuition
Line sweep treats intervals as events on a number line. At each interval's start, we increment an "active" counter; at each interval's end plus one, we decrement it. When the active count equals 2, it means both an interval from the first list and one from the second list are covering that point simultaneously. By processing all events in sorted order, we can identify exactly where overlaps occur without directly comparing pairs of intervals.
Algorithm
- Create a map to store events. For each interval
[start, end]:- Add
+1at positionstart. - Add
-1at positionend + 1.
- Add
- Sort all event positions.
- Traverse the events in order, maintaining an
activecounter:- Before updating the counter, if
active == 2, record the intersection from the previous position to the current position minus one. - Update
activeby adding the event's value. - Track the previous position for the next iteration.
- Before updating the counter, if
- Return the collected intersections.
class Solution:
def intervalIntersection(self, firstList: List[List[int]], secondList: List[List[int]]) -> List[List[int]]:
mp = defaultdict(int)
for s, e in firstList:
mp[s] += 1
mp[e + 1] -= 1
for s, e in secondList:
mp[s] += 1
mp[e + 1] -= 1
res = []
active = 0
prev = None
for x in sorted(mp):
if active == 2:
res.append([prev, x - 1])
active += mp[x]
prev = x
return resTime & Space Complexity
- Time complexity:
- Space complexity:
Where and are the sizes of the arrays and , respectively.
3. Two Pointers
Intuition
Since both interval lists are sorted and disjoint within themselves, we can use two pointers to efficiently find intersections. At each step, we compare the current interval from each list. If they overlap, we record the intersection. Then we advance the pointer for whichever interval ends first, since that interval cannot intersect with any future intervals from the other list. This eliminates unnecessary comparisons and processes each interval exactly once.
Algorithm
- Initialize two pointers
iandjto0, and an empty result list. - While both pointers are within bounds:
- Get the current intervals:
[startA, endA]fromfirstList[i]and[startB, endB]fromsecondList[j]. - Compute the potential intersection:
start = max(startA, startB)andend = min(endA, endB). - If
start <= end, add[start, end]to the result. - Advance the pointer for the interval that ends first (if
endA < endB, incrementi; otherwise, incrementj).
- Get the current intervals:
- Return the result list.
class Solution:
def intervalIntersection(self, firstList: List[List[int]], secondList: List[List[int]]) -> List[List[int]]:
res = []
i = j = 0
while i < len(firstList) and j < len(secondList):
startA, endA = firstList[i]
startB, endB = secondList[j]
start = max(startA, startB)
end = min(endA, endB)
if start <= end:
res.append([start, end])
if endA < endB:
i += 1
else:
j += 1
return resTime & Space Complexity
- Time complexity:
- Space complexity:
- extra space.
- for the output list.
Where and are the sizes of the arrays and , respectively.
Common Pitfalls
Incorrect Overlap Detection
A common mistake is using an incorrect condition to check if two intervals overlap. The correct condition is start <= end where start = max(startA, startB) and end = min(endA, endB). Some developers mistakenly check if one interval's start is between the other's boundaries, which can miss edge cases like when intervals touch at a single point (e.g., [1,3] and [3,5] should produce [3,3]).
Wrong Pointer Advancement Logic
When using the two-pointer approach, advancing the wrong pointer leads to missed intersections. The key insight is to advance the pointer for the interval that ends first because that interval cannot intersect with any future intervals from the other list. Advancing based on start times or advancing both pointers simultaneously will produce incorrect results.
Forgetting Empty Input Cases
Failing to handle cases where one or both input lists are empty can cause index out-of-bounds errors or incorrect behavior. Always ensure your solution gracefully returns an empty result when either firstList or secondList is empty, which the two-pointer approach handles naturally by never entering the while loop.