Skip to content

304. Interval List Intersections

🔗 LeetCode Problem: 986. Interval List Intersections
📊 Difficulty: Medium
🏷️ Topics: Array, Two Pointers

Problem Statement

You are given two lists of closed intervals, firstList and secondList, where firstList[i] = [starti, endi] and secondList[j] = [startj, endj]. Each list of intervals is pairwise disjoint and in sorted order.

Return the intersection of these two interval lists.

A closed interval [a, b] (with a <= b) denotes the set of real numbers x with a <= x <= b.

The intersection of two closed intervals is a set of real numbers that are either empty or represented as a closed interval. For example, the intersection of [1, 3] and [2, 4] is [2, 3].

Example 1:

Input: firstList = [[0,2],[5,10],[13,23],[24,25]], secondList = [[1,5],[8,12],[15,24],[25,26]]
Output: [[1,2],[5,5],[8,10],[15,23],[24,24],[25,25]]

Example 2:

Input: firstList = [[1,3],[5,9]], secondList = []
Output: []

Constraints: - 0 <= firstList.length, secondList.length <= 1000 - firstList.length + secondList.length >= 1 - 0 <= starti < endi <= 10^9 - endi < starti+1 - 0 <= startj < endj <= 10^9 - endj < startj+1


🌳 Visual Understanding - The Intersection Problem

The Problem We're Solving:

firstList:  [[0,2], [5,10], [13,23], [24,25]]
secondList: [[1,5], [8,12], [15,24], [25,26]]

Timeline visualization:

First:   [0, 2]     [5,     10]       [13,          23] [24,25]
         |---|      |-------|         |---------------|  |-|
Second:      [1,  5]    [8,      12]     [15,         24]  [25,  26]
             |-----|    |---------|      |-------------|   |-----|

Intersections:
  [0,2] ∩ [1,5] = [1,2] ✓
  [5,10] ∩ [1,5] = [5,5] ✓ (single point!)
  [5,10] ∩ [8,12] = [8,10] ✓
  [13,23] ∩ [15,24] = [15,23] ✓
  [24,25] ∩ [15,24] = [24,24] ✓ (single point!)
  [24,25] ∩ [25,26] = [25,25] ✓ (single point!)

Output: [[1,2],[5,5],[8,10],[15,23],[24,24],[25,25]]

Understanding Intersection:

Two intervals [a,b] and [c,d] intersect if they have common points

Intersection exists if:
  a <= d AND c <= b
  (First starts before second ends AND second starts before first ends)

When they intersect, intersection is:
  [max(a,c), min(b,d)]

Examples:
  [1,3] ∩ [2,4] = [max(1,2), min(3,4)] = [2,3] ✓
  [1,5] ∩ [3,7] = [max(1,3), min(5,7)] = [3,5] ✓
  [1,3] ∩ [5,7] = No intersection (3 < 5)

Key Observations:

1. Both lists are SORTED and DISJOINT
   Within each list, no overlaps!
   firstList[i].end < firstList[i+1].start

2. Need to find ALL intersections
   Not just count, but actual intervals

3. Can use TWO POINTERS!
   Since both sorted, process chronologically

4. After checking intersection:
   Advance pointer for interval that ends earlier
   (The one ending later might intersect with next!)

🧠 The AHA Moment - Two Pointers Strategy

Approach 1: Brute Force (For Understanding)

For each interval in firstList:
  For each interval in secondList:
    Check if they intersect
    If yes, add intersection

Time: O(m × n)
Works but inefficient! 🤔

Approach 2: Two Pointers (Optimal!)

Key insight: Both lists are SORTED!

Use two pointers:
  i for firstList
  j for secondList

At each step:
  1. Check if firstList[i] and secondList[j] intersect
  2. If yes, add intersection to result
  3. Move pointer for interval that ends earlier
     (Other interval might intersect with next!)

Time: O(m + n) - Linear!
OPTIMAL! 🎯

Visual Discovery:

firstList:  [[0,2], [5,10]]
secondList: [[1,5], [8,12]]

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Step 1: i=0, j=0
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

firstList[0] = [0,2]
secondList[0] = [1,5]

Check intersection:
  0 <= 5? YES
  1 <= 2? YES
  They intersect!

Intersection: [max(0,1), min(2,5)] = [1,2] ✓

Which ends earlier?
  2 < 5 → first[0] ends earlier
  Move i++

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Step 2: i=1, j=0
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

firstList[1] = [5,10]
secondList[0] = [1,5]

Check intersection:
  5 <= 5? YES
  1 <= 10? YES
  They intersect!

Intersection: [max(5,1), min(10,5)] = [5,5] ✓ (single point!)

Which ends earlier?
  10 < 5? NO, 5 < 10
  second[0] ends earlier (or equal)
  Move j++

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Step 3: i=1, j=1
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

firstList[1] = [5,10]
secondList[1] = [8,12]

Check intersection:
  5 <= 12? YES
  8 <= 10? YES
  They intersect!

Intersection: [max(5,8), min(10,12)] = [8,10] ✓

Which ends earlier?
  10 < 12 → first[1] ends earlier
  Move i++

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Step 4: i=2, j=1
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

i >= firstList.length
Stop!

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
RESULT: [[1,2], [5,5], [8,10]]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

🟢 Approach: Two Pointers (Optimal)

🎨 Building the Complete Solution

Step 1: Initialize Two Pointers

i = 0 (pointer for firstList)
j = 0 (pointer for secondList)
result = []

Step 2: Process While Both Valid

While i < firstList.length AND j < secondList.length:

  Get intervals:
    first = firstList[i]
    second = secondList[j]

  Check intersection:
    If first.start <= second.end AND second.start <= first.end:
      // They intersect!
      intersection = [max(first.start, second.start), 
                     min(first.end, second.end)]
      Add intersection to result

  Move pointer:
    If first.end < second.end:
      i++ (first ends earlier)
    Else:
      j++ (second ends earlier or same)

Step 3: Return Result

Return all intersections found

📝 Implementation - Clean and Clear

import java.util.*;

class Solution {
    public int[][] intervalIntersection(int[][] firstList, int[][] secondList) {
        List<int[]> result = new ArrayList<>();
        int i = 0, j = 0;

        while (i < firstList.length && j < secondList.length) {
            // Get current intervals
            int[] first = firstList[i];
            int[] second = secondList[j];

            // Check if they intersect
            // Condition: first.start <= second.end AND second.start <= first.end
            if (first[0] <= second[1] && second[0] <= first[1]) {
                // They intersect! Find intersection
                int start = Math.max(first[0], second[0]);
                int end = Math.min(first[1], second[1]);
                result.add(new int[]{start, end});
            }

            // Move pointer for interval that ends earlier
            if (first[1] < second[1]) {
                i++;
            } else {
                j++;
            }
        }

        return result.toArray(new int[result.size()][]);
    }
}

🔍 Complete Dry Run - Example 1

Input: 
firstList = [[0,2],[5,10],[13,23],[24,25]]
secondList = [[1,5],[8,12],[15,24],[25,26]]

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
INITIALIZATION
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

i = 0, j = 0
result = []

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Iteration 1: i=0, j=0
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

first = [0,2], second = [1,5]

Check intersection:
  0 <= 5? YES
  1 <= 2? YES
  → Intersect!

Intersection: [max(0,1), min(2,5)] = [1,2]
result = [[1,2]]

Which ends earlier?
  2 < 5 → i++ → i=1

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Iteration 2: i=1, j=0
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

first = [5,10], second = [1,5]

Check intersection:
  5 <= 5? YES
  1 <= 10? YES
  → Intersect!

Intersection: [max(5,1), min(10,5)] = [5,5]
result = [[1,2], [5,5]]

Which ends earlier?
  10 < 5? NO → j++ → j=1

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Iteration 3: i=1, j=1
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

first = [5,10], second = [8,12]

Check intersection:
  5 <= 12? YES
  8 <= 10? YES
  → Intersect!

Intersection: [max(5,8), min(10,12)] = [8,10]
result = [[1,2], [5,5], [8,10]]

Which ends earlier?
  10 < 12 → i++ → i=2

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Iteration 4: i=2, j=1
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

first = [13,23], second = [8,12]

Check intersection:
  13 <= 12? NO
  → No intersection!

Which ends earlier?
  23 < 12? NO → j++ → j=2

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Iteration 5: i=2, j=2
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

first = [13,23], second = [15,24]

Check intersection:
  13 <= 24? YES
  15 <= 23? YES
  → Intersect!

Intersection: [max(13,15), min(23,24)] = [15,23]
result = [[1,2], [5,5], [8,10], [15,23]]

Which ends earlier?
  23 < 24 → i++ → i=3

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Iteration 6: i=3, j=2
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

first = [24,25], second = [15,24]

Check intersection:
  24 <= 24? YES
  15 <= 25? YES
  → Intersect!

Intersection: [max(24,15), min(25,24)] = [24,24]
result = [[1,2], [5,5], [8,10], [15,23], [24,24]]

Which ends earlier?
  25 < 24? NO → j++ → j=3

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Iteration 7: i=3, j=3
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

first = [24,25], second = [25,26]

Check intersection:
  24 <= 26? YES
  25 <= 25? YES
  → Intersect!

Intersection: [max(24,25), min(25,26)] = [25,25]
result = [[1,2], [5,5], [8,10], [15,23], [24,24], [25,25]]

Which ends earlier?
  25 < 26 → i++ → i=4

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Loop ends: i=4 >= firstList.length
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Return: [[1,2],[5,5],[8,10],[15,23],[24,24],[25,25]] ✓

Complete Dry Run - Example 2 (Edge Case)

Input: firstList = [[1,3],[5,9]], secondList = []

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

i = 0, j = 0
secondList.length = 0

Loop condition: i < 2 AND j < 0?
  0 < 2 (YES) AND 0 < 0 (NO)
  → Condition FALSE

Loop never executes!

Return: [] ✓

📊 Complexity Analysis

Time Complexity: O(m + n)

m = length of firstList
n = length of secondList

Each pointer moves forward only
Each interval visited at most once
Total: O(m + n)

LINEAR! Optimal! 🎯

Space Complexity: O(1) or O(k)

O(1) excluding output space
Output space: O(k) where k = number of intersections
  In worst case: k = min(m, n)


🎯 Key Insights Summary

The Three Critical Points:

1. Two Pointers on Sorted Lists

Both lists sorted → use two pointers!

Process chronologically:
  Compare current intervals from both lists
  Find intersection if exists
  Move pointer strategically

This is the KEY pattern! 🔑

2. Intersection Formula

Two intervals [a,b] and [c,d] intersect if:
  a <= d AND c <= b

When they intersect:
  intersection = [max(a,c), min(b,d)]

This formula is CRITICAL to remember! ✓

3. Which Pointer to Move

After checking intersection:
  Move pointer for interval that ENDS EARLIER

Why?
  The interval ending later might still intersect
  with the NEXT interval from the other list!

This ensures we don't miss any intersections! 🎯


💡 Why This Approach Works - Deep Intuition

The Sorting Property:

Key property: Both lists are SORTED

This means:
  If firstList[i] doesn't intersect with secondList[j],
  and firstList[i] ends before secondList[j],
  then firstList[i] won't intersect with secondList[j+1], j+2, ...
  (because they all start even later!)

Therefore: Safe to move to next interval! ✓

Why Move the Earlier-Ending Interval:

Scenario: first = [5,10], second = [8,15]

If we move second (ending later):
  We might miss intersection of [5,10] with next from second!

If we move first (ending earlier):
  [5,10] already covered all possible intersections
  (Any future second intervals start after 10)
  Safe to move! ✓

Always move the earlier-ending interval! 🎯

⚠️ Common Mistakes

// Mistake 1: Wrong intersection condition
if (first[0] < second[1] && second[0] < first[1]) { // ❌ Should be <=
// ✓ CORRECT: Use <= to handle touching intervals
if (first[0] <= second[1] && second[0] <= first[1]) {

// Mistake 2: Wrong intersection calculation
int start = Math.min(first[0], second[0]); // ❌ Should be max!
int end = Math.max(first[1], second[1]);   // ❌ Should be min!
// ✓ CORRECT:
int start = Math.max(first[0], second[0]);
int end = Math.min(first[1], second[1]);

// Mistake 3: Wrong pointer advancement
if (first[1] <= second[1]) { // ❌ Both might advance when equal!
// ✓ CORRECT: Use < for tie-breaking
if (first[1] < second[1]) {
    i++;
} else {
    j++;
}

// Mistake 4: Not handling empty lists
// ❌ No edge case check needed!
// Loop naturally handles empty lists (condition fails immediately)
// This is actually GOOD - no special case needed!

// Mistake 5: Trying to handle non-intersecting separately
if (!intersect) {
    // Some complex logic ❌
}
// ✓ CORRECT: Just move pointer, no need for special case!
// The pointer movement handles it naturally

📝 Quick Revision Notes

🎯 Core Concept

Problem: Find all intersections between two sorted interval lists

Key Insight: Two pointers on sorted lists!

Algorithm: 1. Use two pointers (i, j) 2. Check if current intervals intersect 3. If yes: add [max(starts), min(ends)] 4. Move pointer for earlier-ending interval

Why It Works: Sorted lists + greedy pointer movement ensures all intersections found!

⚡ Quick Implementation

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class Solution {
  public int[][] intervalIntersection(int[][] firstList, int[][] secondList) {
    return twoPointers(firstList, secondList);
  }

  private int[][] twoPointers(int[][] firstList, int[][] secondList) {
    List<int[]> list = new ArrayList<>();

    // step 1: initialize pointers
    int i = 0;
    int j = 0;

    while (i < firstList.length && j < secondList.length) {
      int[] first = firstList[i];
      int[] second = secondList[j];

      // step 2: decide intersection point
      // if first(a, b) and second(c, d) overlap, then a <= d and c <= b
      // (0, 2) and (1, 5) => 0 <= 5 and 1 <= 2
      // intersection: (max(0, 1), min(2, 5))
      if (first[0] <= second[1] && second[0] <= first[1]) {
        list.add(new int[] { Math.max(first[0], second[0]), Math.min(first[1], second[1]) });
      }

      // step 3: proceed to next step or check
      // first list => (0,2),(5,10) and
      // second list => (1,5),(8,12)
      // We are done with i = 0 which is (0,2) and j = 0 which is (1,5)
      // Now what to move, i or j?
      if (first[1] < second[1]) {
        // case 1: why i++?
        // firstt: [------]
        // second: [----------]
        // first ends earlier. Even if second overlaps first now, first is done after
        // this point. No future interval in secondList can overlap this first
        // interval, because future second intervals start even later. So, move i.
        i++;
      } else {
        // case 2: why j++?
        // firstt: [----------]
        // second: [------]
        // second ends earlier.
        // Same logic: second cannot overlap anything else in firstList. So, move j.
        j++;
      }
    }

    return list.toArray(new int[list.size()][]);
  }

  public static void main(String[] args) {
    Solution s = new Solution();

    // [[1,2],[5,5],[8,10],[15,23],[24,24],[25,25]]
    System.out
        .println(Arrays.deepToString(s.intervalIntersection(new int[][] { { 0, 2 }, { 5, 10 }, { 13, 23 }, { 24, 25 } },
            new int[][] { { 1, 5 }, { 8, 12 }, { 15, 24 }, { 25, 26 } })));

    // []
    System.out.println(Arrays.deepToString(s.intervalIntersection(new int[][] { { 1, 3 }, { 5, 9 } }, new int[][] {})));
  }
}

🔑 Key Points

✓ Two pointers on sorted lists
✓ Intersection check: a<=d AND c<=b
✓ Intersection: [max(starts), min(ends)]
✓ Move earlier-ending interval pointer
✓ Use < not <= for pointer advancement
✓ Time: O(m+n), Space: O(1)
✓ No special handling for empty lists needed

🎪 Memory Aid

"Two pointers march in line!"
"Max of starts, min of ends define!"
"Earlier ending, forward we incline!"
"Sorted lists make intersections shine!"


🎉 Congratulations!

You've mastered Interval List Intersections!

What You Learned:

Two pointers on sorted lists - Classic pattern
Intersection formula - Max of starts, min of ends
Strategic pointer movement - Earlier-ending advances
Linear time solution - O(m+n) optimal
Clean boundary handling - No special cases needed

The Beautiful Insight:

Sorted Lists → Two Pointers → Linear Solution

The core insight:
  "Both lists sorted → process chronologically"
  "Compare current from each, find intersection"
  "Move strategically to avoid missing any"

  Intersection formula:
    Check: a <= d AND c <= b
    Result: [max(a,c), min(b,d)]

  Pointer strategy:
    Move the earlier-ending one
    (Later-ending might intersect with next!)

  This gives LINEAR time - optimal!

This two-pointer pattern appears everywhere:
  - Merge sorted lists
  - Find common elements
  - Range intersections
  - Any sorted data comparison

Master this = Recognize merge opportunities! ✨

Interview Mastery:

When asked about Interval List Intersections:

1. Understand: "Find all intersections between two sorted lists"

2. Clarify: "Both lists are sorted and disjoint, right?"

3. Key insight: "Both sorted → use two pointers!"

4. Intersection check: "Intervals [a,b] and [c,d] intersect if
   a <= d AND c <= b. Simple cross-check!"

5. Intersection formula: "When they intersect:
   [max(a,c), min(b,d)]
   Max of starts, min of ends!"

6. Pointer movement: "Move pointer for earlier-ending interval.
   Why? Later-ending might intersect with next from other list!"

7. Code it: Clean O(m+n) solution

8. Complexity: "O(m+n) time - visit each interval once.
   O(1) space excluding output."

9. Edge cases: "Empty lists handled naturally by loop condition!"

Shows complete understanding! 💯

You've mastered a beautiful two-pointer interval problem! This pattern of merging/comparing sorted lists is fundamental! 🚀✨🎯