Intervals
Scope — Sort-then-merge interval problems — merge, insert, count overlaps, minimum removals. See also: scanning_line.md — event-based sweeps when you need a running count; difference_array.md — many range updates, one final read; array_overlap_explanation.md — the overlap predicate itself; heap.md — interval scheduling with a priority queue.
LeetCode Problem Lists
Overview
Intervals are problems involving ranges of values, typically represented as [start, end] pairs, requiring operations like merging overlapping ranges, finding intersections, or scheduling non-overlapping events.
Key Properties
- Time Complexity: O(n log n) for sorting + O(n) for processing = O(n log n) overall
- Space Complexity: O(1) to O(n) depending on output requirements
- Core Idea: Sort intervals by start time, then process linearly to handle overlaps
- When to Use: Problems involving ranges, scheduling, calendar management, resource allocation
Core Algorithm Steps
- Sort intervals by start time (occasionally by end time for greedy problems)
- Process sequentially to identify overlaps or non-overlaps
- Apply merge/remove strategy based on problem requirements
- Handle edge cases like empty intervals or single intervals
When to Use Interval Algorithms
- Merge overlapping ranges: Calendar conflicts, memory allocation
- Scheduling optimization: Meeting rooms, task assignment
- Range queries: Time series data, genomic sequences
- Resource management: Bandwidth allocation, CPU scheduling
References
1) Problem Categories
Pattern 1: Interval Merging — LC 56 Priority 5 of 5 — Must know — expect it in almost every loop
- Description: Combine overlapping intervals into single merged intervals
- Examples: LC 56 (Merge Intervals), LC 57 (Insert Interval)
- Recognition: “Merge”, “combine”, “overlapping intervals”
- Sorting: By start time (ascending)
Pattern 2: Interval Scheduling (Greedy) — LC 435 Priority 4 of 5 — High value — a gap here costs you rounds
- Description: Find maximum non-overlapping intervals or minimum intervals to remove
- Examples: LC 435 (Non-overlapping Intervals), LC 452 (Minimum Arrows)
- Recognition: “Maximum”, “minimum”, “non-overlapping”, “remove”
- Sorting: By end time (ascending) for greedy approach
Pattern 3: Interval Intersection — LC 986 Priority 3 of 5 — Worth knowing — usually a variant of a must-know pattern
- Description: Find common time slots or overlapping regions between interval lists
- Examples: LC 986 (Interval List Intersections), LC 1288 (Remove Covered Intervals)
- Recognition: “Intersection”, “overlap”, “common”, “covered”
- Sorting: By start time, process two pointers
Pattern 4: Interval Point Coverage — LC 452 Priority 3 of 5 — Worth knowing — usually a variant of a must-know pattern
- Description: Determine points that can cover multiple intervals or find gaps
- Examples: LC 452 (Minimum Arrows), LC 1024 (Video Stitching)
- Recognition: “Cover”, “points”, “arrows”, “minimum coverage”
- Sorting: By start or end time depending on strategy
Pattern 5: Meeting Room Scheduling — LC 253 Priority 5 of 5 — Must know — expect it in almost every loop
- Description: Determine meeting room requirements or check scheduling conflicts
- Examples: LC 252 (Meeting Rooms), LC 253 (Meeting Rooms II)
- Recognition: “Meeting”, “conference”, “rooms”, “schedule conflicts”
- Sorting: By start time, use priority queue for room management
Pattern 6: Calendar and Booking — LC 729
- Description: Handle calendar bookings with conflict detection and resolution
- Examples: LC 729 (My Calendar I), LC 731 (My Calendar II), LC 732 (My Calendar III)
- Recognition: “Calendar”, “booking”, “double booking”, “k-booking”
- Sorting: Maintain sorted intervals, binary search for insertion
2) Templates & Algorithms
Template Comparison Table
| Template Type | Use Case | Sorting Strategy | When to Use |
|---|---|---|---|
| Merge Template | Combine overlapping intervals | Sort by start time | LC 56, 57, merging problems |
| Greedy Template | Maximum non-overlapping | Sort by end time | LC 435, 452, scheduling optimization |
| Two Pointer Template | Intersection/comparison | Sort both lists by start | LC 986, comparing interval lists |
| Priority Queue Template | Resource management | Sort by start, heap by end | LC 253, meeting room problems |
| Binary Search Template | Calendar/booking | Maintain sorted order | LC 729-732, dynamic interval insertion |
Universal Interval Template Priority 4 of 5 — High value — a gap here costs you rounds
def solve_interval_problem(intervals):
"""
Universal template for interval problems
"""
# Step 1: Handle edge cases
if not intervals or len(intervals) <= 1:
return intervals
# Step 2: Sort intervals (by start time or end time based on problem)
intervals.sort(key=lambda x: x[0]) # Sort by start time
# intervals.sort(key=lambda x: x[1]) # Sort by end time for greedy problems
# Step 3: Initialize result
result = []
# Step 4: Process intervals sequentially
for current in intervals:
# Step 5: Check overlap condition with last processed interval
if not result or no_overlap_condition(result[-1], current):
result.append(current)
else:
# Step 6: Handle overlap (merge, count, or remove)
handle_overlap(result, current)
return result
def no_overlap_condition(prev, curr):
"""Check if two intervals don't overlap"""
return prev[1] < curr[0] # prev ends before curr starts
def handle_overlap(result, current):
"""Handle overlapping intervals based on problem type"""
# For merging: extend the last interval
result[-1][1] = max(result[-1][1], current[1])
# For counting: increment counter
# For removal: choose which interval to keep
Specific Templates
Template 1: Interval Merging (LC 56, 57)
def merge_intervals(intervals):
"""
Merge overlapping intervals
Time: O(n log n), Space: O(n)
"""
if not intervals:
return []
# Sort by start time
intervals.sort(key=lambda x: x[0])
merged = [intervals[0]]
for current in intervals[1:]:
last = merged[-1]
# No overlap: add current interval
if last[1] < current[0]:
merged.append(current)
# Overlap: merge intervals
else:
last[1] = max(last[1], current[1])
return merged
// Java version
public int[][] merge(int[][] intervals) {
if (intervals.length <= 1) return intervals;
Arrays.sort(intervals, (a, b) -> Integer.compare(a[0], b[0]));
List<int[]> merged = new ArrayList<>();
for (int[] current : intervals) {
if (merged.isEmpty() || merged.get(merged.size() - 1)[1] < current[0]) {
merged.add(current);
} else {
merged.get(merged.size() - 1)[1] = Math.max(
merged.get(merged.size() - 1)[1], current[1]
);
}
}
return merged.toArray(new int[merged.size()][]);
}
Template 2: Greedy Scheduling (LC 435, 452)
def min_intervals_to_remove(intervals):
"""
Find minimum intervals to remove for non-overlapping set
Time: O(n log n), Space: O(1)
"""
if not intervals:
return 0
# Sort by end time (greedy strategy)
intervals.sort(key=lambda x: x[1])
count = 0
prev_end = intervals[0][1]
for i in range(1, len(intervals)):
# Overlap detected
if intervals[i][0] < prev_end:
count += 1 # Remove current interval
else:
prev_end = intervals[i][1] # Update end time
return count
// Java version
public int eraseOverlapIntervals(int[][] intervals) {
if (intervals.length <= 1) return 0;
Arrays.sort(intervals, (a, b) -> Integer.compare(a[1], b[1]));
int count = 0;
int prevEnd = intervals[0][1];
for (int i = 1; i < intervals.length; i++) {
if (intervals[i][0] < prevEnd) {
count++;
} else {
prevEnd = intervals[i][1];
}
}
return count;
}
Template 3: Two Pointer Intersection (LC 986)
Key Idea: build the candidate intersection first, then ask whether it is real. The overlap of
[s1, e1]and[s2, e2]can only start at the later start and end at the earlier end — so computestart_max = max(s1, s2),end_min = min(e1, e2), and the two intervals overlap iffstart_max <= end_min. One comparison does both jobs: it is the overlap test and it hands you the answer interval.
Why start_max <= end_min is the overlap test: the only two ways closed intervals miss each other are e1 < s2 (first is entirely left) or e2 < s1 (first is entirely right). Either one makes the later start exceed the earlier end, so start_max > end_min ⇔ no overlap. It is not (e1 < s2 or s1 > e2) rewritten, without the double negative. Use <=, not < — the intervals are closed, so touching at a point ([5,10] ∩ [1,5] = [5,5]) is a real intersection.
Why advance the pointer that ends first: the interval with the smaller end cannot reach anything further right in the other list (those start after the current one, which already extends at least as far), so it is finished. The one with the larger end may still overlap the next interval opposite it, so it stays.
first = [[0,2],[5,10],[13,23],[24,25]]
second = [[1,5],[8,12],[15,24],[25,26]]
i j first second start_max end_min overlap? move
0 0 [0,2] [1,5] 1 2 yes -> [1,2] i (2 < 5)
1 0 [5,10] [1,5] 5 5 yes -> [5,5] j (5 < 10)
1 1 [5,10] [8,12] 8 10 yes -> [8,10] i
2 1 [13,23] [8,12] 13 12 NO (13 > 12) j
2 2 [13,23] [15,24] 15 23 yes -> [15,23] i
3 2 [24,25] [15,24] 24 24 yes -> [24,24] j
3 3 [24,25] [25,26] 25 25 yes -> [25,25] i -> done
res = [[1,2],[5,5],[8,10],[15,23],[24,24],[25,25]]
# python
# LC 986 - Interval List Intersections
# IDEA: candidate overlap = [max(starts), min(ends)]; keep it iff start_max <= end_min; advance the earlier end
# time = O(m + n), space = O(1) extra (output up to m + n intervals)
def intervalIntersection(firstList, secondList):
res = []
i = 0
j = 0
while i < len(firstList) and j < len(secondList):
start_f, end_f = firstList[i]
start_s, end_s = secondList[j]
# 1. the overlap, if any, starts at the later start and ends at the earlier end
start_max = max(start_f, start_s)
end_min = min(end_f, end_s)
# 2. it is real only if it is non-empty (closed intervals: <=, a single point counts)
if start_max <= end_min:
res.append([start_max, end_min])
# 3. the interval that ends first can overlap nothing further on -> advance it
if end_f < end_s:
i += 1
elif end_f > end_s:
j += 1
else:
# both end together: both are finished
i += 1
j += 1
return res
🚫 Traps: (1) start_max < end_min drops the single-point intersections [5,5], [24,24], [25,25] from the example; (2) advancing by start instead of end skips overlaps — the long interval [13,23] has to stay put while [8,12] and [15,24] pass it; (3) on equal ends, advancing only one pointer is still correct (the next round finds no overlap and moves the other) — advancing both just saves that round.
Template 4: Meeting Rooms with Priority Queue (LC 253)
import heapq
def min_meeting_rooms(intervals):
"""
Find minimum meeting rooms required
Time: O(n log n), Space: O(n)
"""
if not intervals:
return 0
# Sort by start time
intervals.sort(key=lambda x: x[0])
# Min heap to track end times
heap = []
for start, end in intervals:
# If earliest meeting ends before current starts
if heap and heap[0] <= start:
heapq.heappop(heap)
# Add current meeting's end time
heapq.heappush(heap, end)
return len(heap)
// Java version
public int minMeetingRooms(int[][] intervals) {
if (intervals.length == 0) return 0;
Arrays.sort(intervals, (a, b) -> Integer.compare(a[0], b[0]));
PriorityQueue<Integer> heap = new PriorityQueue<>();
for (int[] interval : intervals) {
if (!heap.isEmpty() && heap.peek() <= interval[0]) {
heap.poll();
}
heap.offer(interval[1]);
}
return heap.size();
}
Template 5: Calendar Booking (LC 729)
class MyCalendar:
"""
Calendar with overlap detection using binary search
Time: O(log n) per booking, Space: O(n)
"""
def __init__(self):
self.bookings = []
def book(self, start, end):
# Binary search for insertion position
left, right = 0, len(self.bookings)
while left < right:
mid = (left + right) // 2
if self.bookings[mid][1] <= start:
left = mid + 1
else:
right = mid
# Check overlap with neighbors
if left > 0 and self.bookings[left - 1][1] > start:
return False
if left < len(self.bookings) and self.bookings[left][0] < end:
return False
# No overlap, insert booking
self.bookings.insert(left, [start, end])
return True
3) Problems by Pattern
Merging Pattern Problems
| Problem | LC # | Key Technique | Difficulty | Template |
|---|---|---|---|---|
| Merge Intervals | 56 | Sort by start, merge overlaps | Medium | Merge Template |
| Insert Interval | 57 | Insert and merge | Medium | Merge Template |
| Summary Ranges | 228 | Consecutive number ranges | Easy | Merge Template |
| Data Stream as Disjoint Intervals | 352 | TreeMap/SortedDict | Hard | Merge Template |
| Merge Similar Items | 2363 | Merge by weight | Easy | Merge Template |
Greedy Scheduling Problems
| Problem | LC # | Key Technique | Difficulty | Template |
|---|---|---|---|---|
| Non-overlapping Intervals | 435 | Sort by end, greedy removal | Medium | Greedy Template |
| Minimum Arrows to Burst Balloons | 452 | Sort by end, count arrows | Medium | Greedy Template |
| Maximum Length of Pair Chain | 646 | Sort by second element | Medium | Greedy Template |
| Activity Selection Problem | - | Classic greedy algorithm | Medium | Greedy Template |
| Car Pooling | 1094 | Timeline + capacity | Medium | Greedy Template |
| Partition Labels | 763 | Last-occurrence intervals + one-pass merge | Medium | Merge Template |
| Jump Game II | 45 | Implicit intervals + greedy cover | Medium | Greedy Template |
| Jump Game | 55 | Farthest-reach scan | Medium | Greedy Template |
Intersection & Coverage Problems
| Problem | LC # | Key Technique | Difficulty | Template |
|---|---|---|---|---|
| Interval List Intersections | 986 | Two pointers | Medium | Two Pointer Template |
| Remove Covered Intervals | 1288 | Sort and filter | Medium | Merge Template |
| Find Right Interval | 436 | Binary search | Medium | Binary Search |
| Employee Free Time | 759 | Merge + find gaps | Hard | Merge Template |
| Video Stitching | 1024 | Greedy coverage | Medium | Greedy Template |
| Maximum Area of a Piece of Cake After Horizontal and Vertical Cuts | 1465 | Max gap between sorted cuts | Medium | Gap Scan Template |
| Missing Ranges | 163 | Gap scan over sorted unique values | Medium | Gap Scan Template |
| Find All Numbers Disappeared in an Array II | 4031 | Clamp + dedupe + sort, then gap scan | Medium | Gap Scan Template |
Meeting Room & Scheduling Problems
| Problem | LC # | Key Technique | Difficulty | Template |
|---|---|---|---|---|
| Meeting Rooms | 252 | Sort and check conflicts | Easy | Basic Template |
| Meeting Rooms II | 253 | Priority queue | Medium | Priority Queue Template |
| Meeting Scheduler | 1229 | Two pointers + duration | Medium | Two Pointer Template |
| Minimum Time to Make Rope Colorful | 1578 | Consecutive intervals | Medium | Greedy Template |
| Course Schedule III | 630 | Priority queue + greedy | Hard | Priority Queue Template |
Calendar & Booking Problems
| Problem | LC # | Key Technique | Difficulty | Template |
|---|---|---|---|---|
| My Calendar I | 729 | Sorted list + binary search | Medium | Calendar Template |
| My Calendar II | 731 | Double booking detection | Medium | Calendar Template |
| My Calendar III | 732 | K-booking with timeline | Hard | Calendar Template |
| Exam Room | 855 | Max gap maintenance | Medium | Binary Search |
| Range Module | 715 | Segment tree/intervals | Hard | Advanced Template |
Advanced Interval Problems
| Problem | LC # | Key Technique | Difficulty | Template |
|---|---|---|---|---|
| Falling Squares | 699 | Coordinate compression | Hard | Advanced Template |
| The Skyline Problem | 218 | Sweep line + priority queue | Hard | Advanced Template |
| Rectangle Area II | 850 | Coordinate compression | Hard | Advanced Template |
| Perfect Rectangle | 391 | Area calculation + validation | Hard | Advanced Template |
| Count Integers in Intervals | 2276 | Dynamic intervals | Hard | Advanced Template |
4) Pattern Selection Strategy
Decision Framework Flowchart
Problem Analysis for Interval Problems:
1. Are you merging overlapping intervals?
├── YES → Use Merge Template (LC 56, 57)
│ ├── Single interval insertion? → Insert Interval Template
│ └── Multiple overlaps? → Standard Merge Template
└── NO → Continue to 2
2. Are you finding maximum non-overlapping intervals?
├── YES → Use Greedy Template (LC 435, 452)
│ ├── Sort by end time
│ └── Greedy selection strategy
└── NO → Continue to 3
3. Are you finding intersections between interval lists?
├── YES → Use Two Pointer Template (LC 986)
│ ├── Two sorted lists? → Standard Two Pointer
│ └── Multiple lists? → Merge then process
└── NO → Continue to 4
4. Are you managing meeting rooms or resources?
├── YES → Use Priority Queue Template (LC 253)
│ ├── Count resources needed? → Min heap approach
│ └── Check availability? → Sort + scan
└── NO → Continue to 5
5. Are you handling dynamic bookings/calendar?
├── YES → Use Calendar Template (LC 729-732)
│ ├── Single booking? → Binary search insertion
│ ├── Double booking allowed? → Two lists approach
│ └── K-booking? → Timeline/sweep line
└── NO → Consider Advanced Templates
6. Advanced cases (Skyline, Rectangles, etc.)
├── Coordinate compression needed?
├── Sweep line algorithm required?
└── Segment tree for range operations?
Template Selection Guide
Quick Decision Tree:
- Overlap Detection:
prev[1] >= curr[0](assuming sorted by start) - Merge Strategy: Extend
prev[1] = max(prev[1], curr[1]) - Greedy Strategy: Sort by end time, keep earliest ending
- Resource Management: Use min heap for end times
- Dynamic Insertion: Maintain sorted order with binary search
5) Key Patterns & Overlap Detection
Overlap Detection Methods
Method 1: After Sorting by Start Time
def has_overlap(interval1, interval2):
"""Check if two intervals overlap (sorted by start)"""
return interval1[1] > interval2[0]
Method 2: General Case (Any Order)
def has_overlap(interval1, interval2):
"""Check if two intervals overlap (any order)"""
start1, end1 = interval1
start2, end2 = interval2
return start1 < end2 and start2 < end1
Overlap Visualization
Case 1 - No Overlap:
|----| interval1
|----| interval2
Case 2 - Overlap:
|-------|
|-------|
Case 3 - Complete Overlap:
|-----------|
|-----|
Common Interval Operations
def merge_two_intervals(a, b):
"""Merge two overlapping intervals"""
return [min(a[0], b[0]), max(a[1], b[1])]
def interval_length(interval):
"""Calculate interval length"""
return interval[1] - interval[0]
def intervals_intersection(a, b):
"""Find intersection of two intervals"""
start = max(a[0], b[0])
end = min(a[1], b[1])
return [start, end] if start <= end else None
def point_in_interval(point, interval):
"""Check if point is in interval"""
return interval[0] <= point <= interval[1]
6) Summary & Quick Reference
Complexity Quick Reference
| Operation | Time | Space | Notes |
|---|---|---|---|
| Sort intervals | O(n log n) | O(1) | Essential first step |
| Merge overlapping | O(n) | O(n) | After sorting |
| Find intersections | O(m + n) | O(m + n) | Two pointer approach; output can hold m + n intervals |
| Meeting rooms | O(n log n) | O(n) | Priority queue for end times |
| Calendar booking | O(log n) | O(n) | Binary search per insertion |
| Greedy scheduling | O(n log n) | O(1) | Sort by end time |
Template Quick Reference
| Template | Pattern | Key Code |
|---|---|---|
| Merge | Overlapping intervals | if last[1] < curr[0]: append else: merge |
| Greedy | Non-overlapping max | sort(key=end); if curr[0] >= prev[1]: count++ |
| Two Pointer | List intersection | start=max(starts), end=min(ends); keep if start <= end; advance earlier end |
| Priority Queue | Resource management | heappush(end_time); if heap[0] <= start: heappop |
| Binary Search | Dynamic insertion | bisect.insort or custom binary search |
Common Patterns & Tricks
Pattern 1: Merge Overlapping
# Standard merging after sorting
intervals.sort()
merged = [intervals[0]]
for curr in intervals[1:]:
if merged[-1][1] < curr[0]:
merged.append(curr)
else:
merged[-1][1] = max(merged[-1][1], curr[1])
Pattern 2: Greedy Selection
# Sort by end time for optimal selection
intervals.sort(key=lambda x: x[1])
count = 1
prev_end = intervals[0][1]
for start, end in intervals[1:]:
if start >= prev_end:
count += 1
prev_end = end
Pattern 3: Timeline Events
# Convert intervals to events for sweep line
events = []
for start, end in intervals:
events.append((start, 1)) # start event
events.append((end, -1)) # end event
events.sort()
Problem-Solving Steps
- Identify Pattern: Merging, scheduling, intersection, or resource management?
- Choose Sorting Strategy: By start time (merging) or end time (greedy)
- Select Template: Use appropriate template from above
- Handle Edge Cases: Empty arrays, single intervals, identical intervals
- Optimize: Consider space optimization for large datasets
Common Mistakes & Tips
🚫 Common Mistakes:
- Wrong sorting order: Sorting by start when should sort by end (greedy problems)
- Off-by-one errors: Using
<=vs<in overlap conditions - Edge case handling: Not checking empty arrays or single intervals
- Merge logic errors: Forgetting to update both start and end during merge
- Greedy strategy confusion: Not understanding why sorting by end time works
- Space complexity: Creating unnecessary intermediate data structures
✅ Best Practices:
- Always sort first: Most interval problems require sorted input
- Clear overlap definition: Define overlap condition clearly before coding
- Use appropriate template: Match template to problem pattern
- Test edge cases: Empty input, single interval, identical intervals
- Visualize examples: Draw intervals to understand overlap patterns
- Choose right sorting key: Start time for merging, end time for greedy
Interview Tips
- Start with examples: Draw intervals on paper to visualize
- Clarify edge cases: What about empty intervals? Point intervals?
- Explain sorting choice: Why sorting by start/end time?
- Walk through algorithm: Show merge/greedy logic step by step
- Optimize incrementally: Start with working solution, then optimize
- Practice common patterns: Master the 5 main templates above
- Time complexity analysis: Always explain O(n log n) sorting + O(n) processing
Data Structure Conversion Tricks
List to Array (Java)
List<int[]> result = new ArrayList<>();
// ... populate result
return result.toArray(new int[result.size()][]);
Efficient Merging in Python
# Using list comprehension for functional style
def merge_intervals(intervals):
intervals.sort()
result = [intervals[0]]
[result.append(curr) if result[-1][1] < curr[0]
else result[-1].__setitem__(1, max(result[-1][1], curr[1]))
for curr in intervals[1:]]
return result
Related Topics
- Greedy Algorithms: Interval scheduling optimization
- Binary Search: Calendar booking and insertion problems
- Priority Queue: Meeting room and resource management
- Two Pointers: Intersection and comparison problems
- Sweep Line: Advanced problems like skyline and rectangles
- Segment Trees: Range updates and queries on intervals
LC Examples
2-1) Merge Intervals (LC 56) — Sort + Merge
Sort by start time; merge overlapping intervals by comparing with last merged.
// LC 56 - Merge Intervals
// IDEA: Sort by start, merge when current.start <= last.end
// time = O(N log N), space = O(N)
public int[][] merge(int[][] intervals) {
Arrays.sort(intervals, (a, b) -> Integer.compare(a[0], b[0]));
List<int[]> merged = new ArrayList<>();
for (int[] interval : intervals) {
if (merged.isEmpty() || merged.get(merged.size()-1)[1] < interval[0]) {
merged.add(interval);
} else {
merged.get(merged.size()-1)[1] = Math.max(merged.get(merged.size()-1)[1], interval[1]);
}
}
return merged.toArray(new int[merged.size()][]);
}
2-2) Non-overlapping Intervals (LC 435) — Greedy Interval Scheduling
Sort by end time; greedily keep intervals that end earliest to minimize removals.
// LC 435 - Non-overlapping Intervals
// IDEA: Greedy — sort by end, count overlapping intervals to remove
// time = O(N log N), space = O(1)
public int eraseOverlapIntervals(int[][] intervals) {
Arrays.sort(intervals, (a, b) -> Integer.compare(a[1], b[1]));
int removals = 0, prevEnd = Integer.MIN_VALUE;
for (int[] interval : intervals) {
if (interval[0] < prevEnd) {
removals++; // overlap: remove current (keep the one ending earlier)
} else {
prevEnd = interval[1];
}
}
return removals;
}
2-3) Insert Interval (LC 57) — Linear Scan + Merge
Key point — the list is already sorted and disjoint, so one pass has three phases: copy what ends before the new interval, merge everything that overlaps it, copy the rest.
Reduces to: Template 1 — merging, without the sort.
# python
# LC 57 - Insert Interval
# IDEA: one forward pointer, three phases: before / merge-overlap / after
# time = O(n), space = O(n) for the output
def insert(intervals, newInterval):
res = []
i, n = 0, len(intervals)
while i < n and intervals[i][1] < newInterval[0]: # ends before the new one starts
res.append(intervals[i])
i += 1
start, end = newInterval
while i < n and intervals[i][0] <= end: # overlaps (touching counts)
start = min(start, intervals[i][0])
end = max(end, intervals[i][1])
i += 1
res.append([start, end])
while i < n:
res.append(intervals[i])
i += 1
return res
// LC 57 - Insert Interval
// IDEA: Three phases — add non-overlapping left, merge overlapping, add right
// time = O(N), space = O(N)
public int[][] insert(int[][] intervals, int[] newInterval) {
List<int[]> result = new ArrayList<>();
int i = 0, n = intervals.length;
// Phase 1: add all intervals that end before newInterval starts
while (i < n && intervals[i][1] < newInterval[0]) result.add(intervals[i++]);
// Phase 2: merge overlapping intervals
while (i < n && intervals[i][0] <= newInterval[1]) {
newInterval[0] = Math.min(newInterval[0], intervals[i][0]);
newInterval[1] = Math.max(newInterval[1], intervals[i][1]);
i++;
}
result.add(newInterval);
// Phase 3: add remaining intervals
while (i < n) result.add(intervals[i++]);
return result.toArray(new int[result.size()][]);
}
2-4) Meeting Rooms II (LC 253) — Min-Heap on End Times
Sort by start; heap tracks earliest ending room — reuse if room ends before next meeting.
// LC 253 - Meeting Rooms II
// IDEA: Sort by start; min-heap of end times — reuse room if heap.peek() <= start
// time = O(N log N), space = O(N)
public int minMeetingRooms(int[][] intervals) {
Arrays.sort(intervals, (a, b) -> Integer.compare(a[0], b[0]));
PriorityQueue<Integer> heap = new PriorityQueue<>();
for (int[] iv : intervals) {
if (!heap.isEmpty() && heap.peek() <= iv[0]) heap.poll();
heap.offer(iv[1]);
}
return heap.size();
}
2-5) Minimum Number of Arrows to Burst Balloons (LC 452) — Greedy
Sort by end; one arrow at interval’s end bursts all overlapping; advance when gap appears.
// LC 452 - Minimum Number of Arrows to Burst Balloons
// IDEA: Greedy — sort by end; new arrow only when next start > current end
// time = O(N log N), space = O(1)
public int findMinArrowShots(int[][] points) {
Arrays.sort(points, (a, b) -> Integer.compare(a[1], b[1]));
int arrows = 1, end = points[0][1];
for (int i = 1; i < points.length; i++)
if (points[i][0] > end) { arrows++; end = points[i][1]; }
return arrows;
}
2-6) Interval List Intersections (LC 986) — Two Pointers
Advance the pointer whose interval ends first; record overlap when ranges intersect.
// LC 986 - Interval List Intersections
// IDEA: Two pointers — compute intersection, advance pointer with smaller end
// time = O(M+N), space = O(M+N)
public int[][] intervalIntersection(int[][] A, int[][] B) {
List<int[]> res = new ArrayList<>();
int i = 0, j = 0;
while (i < A.length && j < B.length) {
int lo = Math.max(A[i][0], B[j][0]);
int hi = Math.min(A[i][1], B[j][1]);
if (lo <= hi) res.add(new int[]{lo, hi});
if (A[i][1] < B[j][1]) i++;
else j++;
}
return res.toArray(new int[res.size()][]);
}
2-7) Remove Covered Intervals (LC 1288) — Sort + Greedy
Sort by start asc, end desc; interval is covered if its end ≤ current max end.
// LC 1288 - Remove Covered Intervals
// IDEA: Sort start ASC, end DESC; count intervals not covered by running maxEnd
// time = O(N log N), space = O(1)
public int removeCoveredIntervals(int[][] intervals) {
Arrays.sort(intervals, (a, b) -> a[0] != b[0] ? Integer.compare(a[0], b[0]) : Integer.compare(b[1], a[1]));
int count = 0, maxEnd = 0;
for (int[] iv : intervals)
if (iv[1] > maxEnd) { count++; maxEnd = iv[1]; }
return count;
}
2-8) Video Stitching (LC 1024) — Greedy Interval Cover
Sort by start; at each frontier pick the clip extending coverage the furthest.
// LC 1024 - Video Stitching
// IDEA: Greedy — at current end, pick clip reaching farthest next position
// time = O(N log N), space = O(1)
public int videoStitching(int[][] clips, int time) {
Arrays.sort(clips, (a, b) -> Integer.compare(a[0], b[0]));
int count = 0, curEnd = 0, farthest = 0, i = 0;
while (i < clips.length && curEnd < time) {
while (i < clips.length && clips[i][0] <= curEnd)
farthest = Math.max(farthest, clips[i++][1]);
if (farthest == curEnd) return -1;
curEnd = farthest;
count++;
}
return curEnd >= time ? count : -1;
}
2-9) Maximum Profit in Job Scheduling (LC 1235) — DP + Binary Search
Sort jobs by end; dp[i] = max profit using first i jobs; binary search for last non-conflicting job.
// LC 1235 - Maximum Profit in Job Scheduling
// IDEA: Sort by end; DP + binary search for latest non-overlapping job
// time = O(N log N), space = O(N)
public int jobScheduling(int[] startTime, int[] endTime, int[] profit) {
int n = startTime.length;
int[][] jobs = new int[n][3];
for (int i = 0; i < n; i++) jobs[i] = new int[]{endTime[i], startTime[i], profit[i]};
Arrays.sort(jobs, (a, b) -> Integer.compare(a[0], b[0]));
int[] dp = new int[n + 1];
for (int i = 0; i < n; i++) {
int lo = 0, hi = i;
while (lo < hi) {
int mid = (lo + hi + 1) / 2;
if (jobs[mid-1][0] <= jobs[i][1]) lo = mid;
else hi = mid - 1;
}
dp[i+1] = Math.max(dp[i], dp[lo] + jobs[i][2]);
}
return dp[n];
}
2-10) My Calendar I (LC 729) — TreeMap Overlap Check
TreeMap floor/ceiling gives O(log N) overlap detection per booking.
// LC 729 - My Calendar I
// IDEA: TreeMap — O(log N) overlap check with floorKey / ceilingKey
// time = O(log N) per booking, space = O(N)
class MyCalendar {
TreeMap<Integer, Integer> cal = new TreeMap<>();
public boolean book(int start, int end) {
Integer prev = cal.floorKey(start), next = cal.ceilingKey(start);
if ((prev == null || cal.get(prev) <= start) && (next == null || next >= end)) {
cal.put(start, end);
return true;
}
return false;
}
}
2-11) Meeting Rooms I (LC 252) — Sort + Adjacent Check
Sort by start time; if any meeting starts before previous ends, overlap exists.
// LC 252 - Meeting Rooms
// IDEA: Sort by start; adjacent overlap check
// time = O(N log N), space = O(1)
public boolean canAttendMeetings(int[][] intervals) {
Arrays.sort(intervals, (a, b) -> Integer.compare(a[0], b[0]));
for (int i = 1; i < intervals.length; i++)
if (intervals[i][0] < intervals[i-1][1]) return false;
return true;
}
2-12) Partition Labels (LC 763) — Build Intervals From Data, Then Merge Priority 5 of 5 — Must know — expect it in almost every loop
Key Idea: the intervals aren’t given — you construct them. Each character
cowns the interval[first(c), last(c)]; a valid partition is a merged interval. Because we scan left→right, the intervals arrive already sorted by start, so nosort()is needed: keep a runningend = max(end, last[c])and cut the momenti == end.Pattern:
input → derive intervals → mergeis the single most common interval disguise in interviews (also LC 56’s engine, just with implicit input).
Why i == end is the correct cut: end is the max last-occurrence of every character seen so far. When the scan index catches up to it, no character inside [start, i] appears later ⇒ the block is closed and cannot merge with anything to the right.
s = a b a b c b a c a d e f e g d e h i j h k l i j
i: 0 1 2 3 4 5 6 7 8 9 ...
last[a]=8, last[b]=5, last[c]=7 -> end grows 0,5,5,5,7,7,8,8,8 -> cut at i=8 (len 9)
next block starts at 9 ...
// java
// LC 763 - Partition Labels
// IDEA: last occurrence of each char = that char's interval end; extend & cut in one pass (no sort)
// time = O(N), space = O(1) (26 letters)
public List<Integer> partitionLabels(String s) {
int[] last = new int[26];
for (int i = 0; i < s.length(); i++) last[s.charAt(i) - 'a'] = i;
List<Integer> res = new ArrayList<>();
int start = 0, end = 0;
for (int i = 0; i < s.length(); i++) {
end = Math.max(end, last[s.charAt(i) - 'a']); // extend current interval
if (i == end) { // interval closed -> cut here
res.add(end - start + 1);
start = i + 1;
}
}
return res;
}
# python
# LC 763 - Partition Labels
# IDEA: last[c] = that char's interval end; extend running end, cut when i == end
# time = O(N), space = O(1) (26 letters)
def partitionLabels(s):
last = {c: i for i, c in enumerate(s)} # dict comprehension keeps the LAST index
res, start, end = [], 0, 0
for i, c in enumerate(s):
end = max(end, last[c]) # extend current interval
if i == end: # interval closed -> cut here
res.append(end - start + 1)
start = i + 1
return res
Contrast with LC 56 (2-1): LC 56 must sort because intervals arrive in any order; here the scan order is start order, dropping the cost to O(N).
2-13) Jump Game II (LC 45) — Greedy Cover Over Implicit Intervals
Variation of 2-8 (Video Stitching): the twist is that no interval array is given — index
iimplicitly covers[i, i + nums[i]], and those intervals are already sorted by start, so the O(N log N) sort disappears and the greedy cover runs in O(N).Pattern: same two-frontier greedy as LC 1024 —
curEnd= boundary of the coverage bought by the jumps so far,farthest= best reach among all intervals starting inside it. Hittingi == curEndmeans the current layer is exhausted ⇒ spend one more jump.
// java
// LC 45 - Jump Game II
// IDEA: index i = interval [i, i+nums[i]]; greedy cover, +1 jump when current coverage is exhausted
// time = O(N), space = O(1)
public int jump(int[] nums) {
int jumps = 0, curEnd = 0, farthest = 0;
for (int i = 0; i < nums.length - 1; i++) { // stop at n-1: no jump needed once we can reach it
farthest = Math.max(farthest, i + nums[i]);
if (i == curEnd) { // exhausted current layer -> must jump
jumps++;
curEnd = farthest;
}
}
return jumps;
}
# python
# LC 45 - Jump Game II
# IDEA: index i = interval [i, i+nums[i]]; greedy cover, +1 jump when current coverage is exhausted
# time = O(N), space = O(1)
def jump(nums):
jumps = cur_end = farthest = 0
for i in range(len(nums) - 1): # stop before last index
farthest = max(farthest, i + nums[i])
if i == cur_end: # exhausted current layer -> must jump
jumps += 1
cur_end = farthest
return jumps
Variation — LC 55 Jump Game: reachability only, drop the jump counter
Same scan; instead of counting layers, fail the moment the scan index passes the farthest covered position (a gap in the cover).
// java
// LC 55 - Jump Game
// IDEA: same coverage scan; unreachable once i > farthest (gap in the cover)
// time = O(N), space = O(1)
public boolean canJump(int[] nums) {
int farthest = 0;
for (int i = 0; i < nums.length; i++) {
if (i > farthest) return false; // gap: cover breaks here
farthest = Math.max(farthest, i + nums[i]);
}
return true;
}
# python
# LC 55 - Jump Game
# IDEA: same coverage scan; unreachable once i > farthest (gap in the cover)
# time = O(N), space = O(1)
def canJump(nums):
farthest = 0
for i, n in enumerate(nums):
if i > farthest:
return False # gap: cover breaks here
farthest = max(farthest, i + n)
return True
| Problem | Given intervals? | Sort needed? | Question asked |
|---|---|---|---|
| LC 1024 Video Stitching | explicit clips[i] = [s, e] |
YES — O(N log N) | min clips to cover [0, time] |
| LC 45 Jump Game II | implicit [i, i+nums[i]] |
NO — already start-sorted | min intervals to reach n-1 |
| LC 55 Jump Game | implicit [i, i+nums[i]] |
NO | is [0, n-1] coverable at all |
2-14) Maximum Area of a Piece of Cake (LC 1465) — Gap Scan (Interval Complement)
Key Idea: the complement of a set of cut points is a set of intervals. After sorting the boundaries, the pieces are simply the consecutive differences — plus the two border gaps (
0 → first cutandlast cut → border), which is where nearly every wrong answer comes from.Pattern: the same “gaps between sorted boundaries” scan powers free-slot problems (e.g. LC 759 Employee Free Time gaps after merging, LC 228 Summary Ranges). Here the two axes are independent, so
maxArea = maxGap(h) * maxGap(w).
// java
// LC 1465 - Maximum Area of a Piece of Cake After Horizontal and Vertical Cuts
// IDEA: complement of sorted cuts = piece intervals; max gap per axis, multiply (axes independent)
// time = O(H log H + V log V), space = O(1)
public int maxArea(int h, int w, int[] horizontalCuts, int[] verticalCuts) {
Arrays.sort(horizontalCuts);
Arrays.sort(verticalCuts);
long maxH = maxGap(horizontalCuts, h);
long maxV = maxGap(verticalCuts, w);
return (int) ((maxH * maxV) % 1_000_000_007L); // multiply as long: 1e9 * 1e9 overflows int
}
private long maxGap(int[] cuts, int border) {
long best = cuts[0]; // border gap: 0 -> first cut
for (int i = 1; i < cuts.length; i++)
best = Math.max(best, cuts[i] - cuts[i-1]); // inner gaps: cut -> cut
return Math.max(best, border - cuts[cuts.length - 1]); // border gap: last cut -> border
}
# python
# LC 1465 - Maximum Area of a Piece of Cake After Horizontal and Vertical Cuts
# IDEA: complement of sorted cuts = piece intervals; max gap per axis, multiply (axes independent)
# time = O(H log H + V log V), space = O(1)
def maxArea(h, w, horizontalCuts, verticalCuts):
def max_gap(cuts, border):
cuts = sorted(cuts)
best = max(cuts[0], border - cuts[-1]) # the two border gaps
for a, b in zip(cuts, cuts[1:]):
best = max(best, b - a) # inner gaps
return best
return (max_gap(horizontalCuts, h) * max_gap(verticalCuts, w)) % (10 ** 9 + 7)
🚫 Traps: (1) forgetting the two border gaps; (2) taking % on each factor before multiplying in Java int — use long for the product; (3) assuming cuts arrive sorted (they do not).
2-15) Find All Numbers Disappeared in an Array II (LC 4031) — Gap Scan With a Sentinel Priority 4 of 5 — High value — a gap here costs you rounds
Key Idea: same interval complement scan as 2-14, but the answer is the gaps themselves: sort the values, and every jump from
prevtoxlarger than 1 exposes the missing block[prev+1, x-1].Two things make this harder than LC 163 (Missing Ranges), which hands you a sorted, duplicate-free array already inside
[lower, upper]. Herenumsis unsorted, may hold duplicates, and may hold values outside the range — all three break the scan, and all three are fixed by onesorted(set(...))over the clamped values before the loop.The sentinel
prev = lower - 1is what removes the leading special case: the first real value is then compared againstlowerexactly like every inner value is compared against its predecessor. The trailing gap has no such trick — nothing follows the last value, so it needs an explicit check after the loop. Forgetting it is the standard wrong answer.
nums = [3,9,7], lower = 1, upper = 12
clamp + dedupe + sort -> [3, 7, 9]
prev=0 x=3 -> 3 > 0+1 -> emit [1, 2] prev=3
x=7 -> 7 > 3+1 -> emit [4, 6] prev=7
x=9 -> 9 > 7+1 -> emit [8, 8] prev=9 (single missing number = width-1 range)
after loop: prev=9 < 12 -> emit [10, 12] (trailing gap: NOT covered by the loop)
res = [[1,2], [4,6], [8,8], [10,12]]
# python
# LC 4031 - Find All Numbers Disappeared in an Array II
# IDEA: clamp to [lower, upper] + dedupe + sort, then emit each gap between consecutive values
# time = O(N log N), space = O(N)
def findDisappearedNumbers(nums, lower, upper):
nums = sorted({x for x in nums if lower <= x <= upper})
res = []
prev = lower - 1 # sentinel: makes the leading gap an ordinary gap
for x in nums:
if x > prev + 1: # a hole between prev and x
res.append([prev + 1, x - 1])
prev = x
if prev < upper: # trailing gap: the loop can never emit it
res.append([prev + 1, upper])
return res
// java
// LC 4031 - Find All Numbers Disappeared in an Array II
// IDEA: TreeSet clamps + dedupes + sorts in one pass; then the same gap scan
// time = O(N log N), space = O(N)
public List<List<Integer>> findDisappearedNumbers(int[] nums, int lower, int upper) {
TreeSet<Integer> seen = new TreeSet<>();
for (int x : nums)
if (x >= lower && x <= upper) seen.add(x);
List<List<Integer>> res = new ArrayList<>();
int prev = lower - 1; // sentinel
for (int x : seen) {
if (x > prev + 1) res.add(Arrays.asList(prev + 1, x - 1));
prev = x;
}
if (prev < upper) res.add(Arrays.asList(prev + 1, upper)); // trailing gap
return res;
}
🚫 Traps: (1) dropping the trailing gap, or the leading one if you start from nums[0] instead of the sentinel; (2) not filtering values outside [lower, upper] — one stray x < lower makes prev run backwards and emits garbage; (3) not deduping — a repeated value gives x == prev, so x > prev + 1 is false and nothing breaks here, but the same array in the LC 163 two-pointer form emits an inverted range [prev+1, x-1]; (4) [8,8] is a legal answer — a single missing number is a range whose ends coincide, not a value to be skipped.
| Problem | Input guarantees | Extra work before the scan |
|---|---|---|
| LC 163 Missing Ranges | sorted, unique, all inside [lower, upper] |
none — scan directly, O(N) |
| LC 228 Summary Ranges | sorted, unique | none — emits the present runs, the complement of this |
| LC 4031 Disappeared Numbers II | none of the above | clamp + dedupe + sort, O(N log N) |