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

  1. Initialize an empty result list.
  2. 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.
  3. 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 res

Time & Space Complexity

  • Time complexity: O(mn)O(m * n)
  • Space complexity:
    • O(1)O(1) extra space.
    • O(m+n)O(m + n) for the output list.

Where mm and nn are the sizes of the arrays firstListfirstList and secondListsecondList, 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

  1. Create a map to store events. For each interval [start, end]:
    • Add +1 at position start.
    • Add -1 at position end + 1.
  2. Sort all event positions.
  3. Traverse the events in order, maintaining an active counter:
    • Before updating the counter, if active == 2, record the intersection from the previous position to the current position minus one.
    • Update active by adding the event's value.
    • Track the previous position for the next iteration.
  4. 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 res

Time & Space Complexity

  • Time complexity: O((m+n)log(m+n))O((m + n) \log (m + n))
  • Space complexity: O(m+n)O(m + n)

Where mm and nn are the sizes of the arrays firstListfirstList and secondListsecondList, 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

  1. Initialize two pointers i and j to 0, and an empty result list.
  2. While both pointers are within bounds:
    • Get the current intervals: [startA, endA] from firstList[i] and [startB, endB] from secondList[j].
    • Compute the potential intersection: start = max(startA, startB) and end = min(endA, endB).
    • If start <= end, add [start, end] to the result.
    • Advance the pointer for the interval that ends first (if endA < endB, increment i; otherwise, increment j).
  3. 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 res

Time & Space Complexity

  • Time complexity: O(m+n)O(m + n)
  • Space complexity:
    • O(1)O(1) extra space.
    • O(m+n)O(m + n) for the output list.

Where mm and nn are the sizes of the arrays firstListfirstList and secondListsecondList, 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.