Interval Problem Patterns
Interval problems form a distinct category: they involve ranges [start, end] and typically require detecting overlaps, merging, or scheduling. The canonical operations: sort by start time (almost always the first step), sweep line (process events in sorted order), and greedy scheduling (pick intervals that conflict least). Most medium/hard interval problems are variations of these core patterns. Key insight: two intervals [a, b] and [c, d] overlap if and only if a <= d AND c <= b.
Pattern 1: Merge Intervals (LC 56)
Given a list of intervals, merge all overlapping ones. Sort by start time. Iterate: if the current interval overlaps with the last merged interval (curr.start <= last.end), extend the last interval (last.end = max(last.end, curr.end)). Otherwise, add the current interval as a new entry.
def merge(intervals):
intervals.sort(key=lambda x: x[0])
merged = [intervals[0]]
for start, end in intervals[1:]:
if start <= merged[-1][1]:
merged[-1][1] = max(merged[-1][1], end)
else:
merged.append([start, end])
return merged
Pattern 2: Insert Interval (LC 57)
Insert a new interval into a sorted, non-overlapping list. Three phases: (1) Add all intervals that end before new_interval starts. (2) Merge all intervals that overlap with new_interval (update new_interval’s start and end). (3) Add the merged interval and all remaining intervals.
def insert(intervals, new_interval):
result = []
i, n = 0, len(intervals)
# Phase 1: no overlap, before new_interval
while i < n and intervals[i][1] < new_interval[0]:
result.append(intervals[i]); i += 1
# Phase 2: overlapping intervals
while i < n and intervals[i][0] <= new_interval[1]:
new_interval[0] = min(new_interval[0], intervals[i][0])
new_interval[1] = max(new_interval[1], intervals[i][1])
i += 1
result.append(new_interval)
# Phase 3: remaining
result.extend(intervals[i:])
return result
Pattern 3: Meeting Rooms II (LC 253)
Minimum number of conference rooms to schedule all meetings. Two approaches: Sort start and end times separately. Use two pointers. If the next meeting starts before the earliest-ending meeting ends: need a new room. Else: reuse the room (advance end pointer).
def min_meeting_rooms(intervals):
starts = sorted(i[0] for i in intervals)
ends = sorted(i[1] for i in intervals)
rooms = 0
max_rooms = 0
s = e = 0
while s < len(starts):
if starts[s] < ends[e]:
rooms += 1; s += 1
else:
rooms -= 1; e += 1
max_rooms = max(max_rooms, rooms)
return max_rooms
Alternative: min-heap approach. Sort by start time. Use a min-heap of end times. For each meeting: if heap[0] <= meeting.start, pop (reuse room). Always push meeting.end. Heap size = rooms needed. O(n log n).
Pattern 4: Employee Free Time (LC 759)
Given schedules of k employees (each as a list of non-overlapping intervals), find the common free time for all employees. Key insight: flatten all intervals into one list, sort by start time, merge overlapping intervals to get all “busy” time, then find the gaps between consecutive busy intervals.
def employee_free_time(schedules):
all_intervals = []
for schedule in schedules:
all_intervals.extend(schedule)
all_intervals.sort(key=lambda x: x.start)
merged = [all_intervals[0]]
for iv in all_intervals[1:]:
if iv.start <= merged[-1].end:
merged[-1].end = max(merged[-1].end, iv.end)
else:
merged.append(iv)
free_times = []
for i in range(1, len(merged)):
free_times.append(Interval(merged[i-1].end, merged[i].start))
return free_times
Other patterns: Non-Overlapping Intervals (LC 435): minimum removals to make non-overlapping. Greedy: sort by end time, greedily keep the interval that ends earliest. Remove the current interval if it overlaps with the last kept. Interval List Intersections (LC 986): find intersections of two sorted interval lists. Two pointers: advance the pointer of the interval that ends first. The intersection (if any) is [max(a.start, b.start), min(a.end, b.end)].
See also: Meta Interview Prep
See also: Coinbase Interview Prep
See also: LinkedIn Interview Prep