DSA Fundamentals: Intervals - From Theory to LeetCode Practice
Master interval problems through theory and practical LeetCode problem solving. Covering interval merging, scheduling conflicts, insertion, and meeting room optimization strategies.
Interval problems are a fundamental category in algorithmic problem-solving, frequently appearing in coding interviews and real-world applications like scheduling systems, calendar management, and resource allocation. These problems require understanding how to efficiently handle overlapping ranges, merge intervals, and optimize resource usage.
This comprehensive guide combines theoretical understanding with practical problem-solving, featuring solutions to essential LeetCode problems that demonstrate core interval manipulation patterns.
Understanding Intervals
Interval Fundamentals
Intervals represent ranges of values, typically defined by a start and end point. In most problems, intervals are represented as pairs [start, end] where start ≤ end.
Key Interval Concepts
1. Interval Overlap:
- Two intervals
[a, b]and[c, d]overlap if:max(a, c) ≤ min(b, d) - Alternatively:
a ≤ dandc ≤ b - Non-overlapping condition:
b < cord < a
2. Interval Merging:
- When intervals overlap, merge them into:
[min(a, c), max(b, d)] - Merged interval covers the entire range of both original intervals
3. Interval Ordering:
- Sorting intervals by start time enables efficient processing
- After sorting, we can process intervals in a single pass
Common Interval Problem Types
1. Merge Intervals:
- Combine overlapping intervals into single intervals
- Result: Non-overlapping intervals covering all original intervals
2. Scheduling Conflicts:
- Determine if intervals can be scheduled without conflicts
- Check if any two intervals overlap
3. Insert Interval:
- Add new interval to sorted list of non-overlapping intervals
- Merge with overlapping intervals if necessary
4. Resource Allocation:
- Find minimum resources needed to handle all intervals
- Example: Minimum meeting rooms required
Time Complexity Analysis
| Operation | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Sort intervals | O(n log n) | O(1) | Required for most interval problems |
| Merge intervals | O(n) | O(n) | After sorting, single pass |
| Check overlaps | O(n) | O(1) | Linear scan after sorting |
| Insert interval | O(n) | O(n) | Linear scan and merge |
Essential LeetCode Problems & Solutions
Let's explore fundamental interval problems that demonstrate core algorithmic patterns.
1. Merge Intervals (LeetCode 56)
Problem: Given an array of intervals where intervals[i] = [start_i, end_i], merge all overlapping intervals and return an array of non-overlapping intervals.
Approach: Sort intervals by start time, then iterate through and merge overlapping intervals.
class Solution:
def merge(self, intervals: List[List[int]]) -> List[List[int]]:
# Sort intervals by start time
intervals.sort(key=lambda x: x[0])
# Initialize result with first interval
merged = [intervals[0]]
# Process remaining intervals
for interval in intervals[1:]:
# Check if current interval overlaps with last merged interval
# Overlap condition: current start <= last end
if merged[-1][1] >= interval[0]:
# Merge: extend end time to maximum of both
merged[-1][1] = max(interval[1], merged[-1][1])
else:
# No overlap: add as new interval
merged.append(interval)
return merged
Time Complexity: O(n log n) - Dominated by sorting
Space Complexity: O(n) - Result array storage
Key Concepts:
- Sorting prerequisite: Sort by start time enables single-pass merge
- Overlap detection:
merged[-1][1] >= interval[0]checks if intervals overlap - Merge operation:
max(interval[1], merged[-1][1])extends end time - Greedy approach: Process intervals in order, merge when possible
- Edge case: Empty intervals list handled by initial check
Example Walkthrough:
Input: [[1,3], [2,6], [8,10], [15,18]]
After sort: [[1,3], [2,6], [8,10], [15,18]] (already sorted)
Step 1: merged = [[1,3]]
Step 2: [2,6] overlaps with [1,3] → merge → [[1,6]]
Step 3: [8,10] doesn't overlap → add → [[1,6], [8,10]]
Step 4: [15,18] doesn't overlap → add → [[1,6], [8,10], [15,18]]
2. Meeting Rooms (LeetCode 252)
Problem: Given an array of meeting time intervals, determine if a person could attend all meetings.
Approach: Sort intervals by start time, check if any adjacent intervals overlap.
class Solution:
def meetingRooms(self, intervals: List[List[int]]) -> bool:
# Sort intervals by start time
intervals.sort(key=lambda x: x[0])
# Check adjacent intervals for overlap
for i in range(1, len(intervals)):
first_meeting = intervals[i-1]
second_meeting = intervals[i]
# Overlap condition: first meeting ends after second starts
if first_meeting[1] > second_meeting[0]:
return False
return True
Time Complexity: O(n log n) - Dominated by sorting
Space Complexity: O(1) - Only using loop variables
Key Concepts:
- Adjacent check: After sorting, only need to check consecutive intervals
- Overlap condition:
first_meeting[1] > second_meeting[0]detects conflict - Early termination: Return False immediately when conflict found
- Single person constraint: Person can only attend one meeting at a time
- Sorting optimization: Without sorting, would need O(n²) comparisons
Example Walkthrough:
Input: [[0,30], [5,10], [15,20]]
After sort: [[0,30], [5,10], [15,20]]
Check 1: [0,30] vs [5,10] → 30 > 5 → OVERLAP → return False
Input: [[7,10], [2,4]]
After sort: [[2,4], [7,10]]
Check 1: [2,4] vs [7,10] → 4 > 7? No → No overlap
All meetings can be attended → return True
3. Insert Interval (LeetCode 57)
Problem: Given a sorted array of non-overlapping intervals and a new interval, insert and merge if necessary.
Approach: Three-phase approach: add intervals before new interval, merge overlapping intervals, add remaining intervals.
class Solution:
def insert(self, intervals: List[List[int]], newInterval: List[int]) -> List[List[int]]:
merged = []
n = len(intervals)
i = 0
# Phase 1: Add all intervals that end before new interval starts
while i < n and intervals[i][1] < newInterval[0]:
merged.append(intervals[i])
i += 1
# Phase 2: Merge all intervals that overlap with new interval
while i < n and intervals[i][0] <= newInterval[1]:
# Merge: update newInterval to cover both
newInterval[0] = min(intervals[i][0], newInterval[0])
newInterval[1] = max(intervals[i][1], newInterval[1])
i += 1
# Add the merged interval
merged.append(newInterval)
# Phase 3: Add all remaining intervals
while i < n:
merged.append(intervals[i])
i += 1
return merged
Time Complexity: O(n) - Single pass through intervals
Space Complexity: O(n) - Result array storage
Key Concepts:
- Three-phase approach: Before, merge, after
- Sorted input advantage: Can process in single pass without sorting
- Overlap detection:
intervals[i][0] <= newInterval[1]checks if intervals overlap - Merge expansion: Update newInterval boundaries to cover all overlapping intervals
- Efficient insertion: O(n) time without needing to sort
Example Walkthrough:
Input: intervals = [[1,2], [3,5], [6,7], [8,10], [12,16]]
newInterval = [4,8]
Phase 1: [1,2] ends before 4 → add → merged = [[1,2]]
Phase 2:
- [3,5] overlaps (3 <= 8) → merge → newInterval = [3,8]
- [6,7] overlaps (6 <= 8) → merge → newInterval = [3,8]
- [8,10] overlaps (8 <= 8) → merge → newInterval = [3,10]
- [12,16] doesn't overlap (12 > 10) → stop
Add merged: merged = [[1,2], [3,10]]
Phase 3: Add remaining → merged = [[1,2], [3,10], [12,16]]
4. Meeting Rooms II (LeetCode 253)
Problem: Given an array of meeting time intervals, find the minimum number of conference rooms required.
Approach: Use two pointers technique with separate start and end arrays. Track number of simultaneous meetings.
class Solution:
def minMeetingRooms(self, intervals: List[List[int]]) -> int:
# Sort intervals by start time
intervals.sort(key=lambda x: x[0])
# Extract start and end times
start = []
end = []
for i in range(len(intervals)):
start.append(intervals[i][0])
end.append(intervals[i][1])
# Sort end times separately
end.sort()
# Two pointers: one for start, one for end
p1, p2 = 0, 0
res, count = 0, 0
# Process all start times
while p1 < len(intervals):
# If meeting starts before one ends, need new room
if start[p1] < end[p2]:
count += 1
p1 += 1
else:
# Meeting ended, free up a room
count -= 1
p2 += 1
# Track maximum rooms needed
res = max(res, count)
return res
Time Complexity: O(n log n) - Sorting dominates
Space Complexity: O(n) - Start and end arrays
Key Concepts:
- Two-pointer technique: Track start and end events separately
- Event-based approach: Treat start as +1 room, end as -1 room
- Greedy counting: Count simultaneous meetings at each point
- Maximum tracking: Answer is maximum count during processing
- Why it works: Maximum overlap = minimum rooms needed
Example Walkthrough:
Input: [[0,30], [5,10], [15,20]]
After sort: [[0,30], [5,10], [15,20]]
start = [0, 5, 15]
end = [10, 20, 30] (sorted)
p1=0, p2=0: start[0]=0 < end[0]=10 → count=1, res=1
p1=1, p2=0: start[1]=5 < end[0]=10 → count=2, res=2
p1=2, p2=0: start[2]=15 > end[0]=10 → count=1, p2=1
p1=2, p2=1: start[2]=15 < end[1]=20 → count=2, res=2
p1=3 (done), p2=1: end[1]=20 → count=1
p1=3, p2=2: end[2]=30 → count=0
Maximum count = 2 → Need 2 rooms
Alternative Approach: Priority Queue (Min Heap)
import heapq
class Solution:
def minMeetingRooms(self, intervals: List[List[int]]) -> int:
# Sort by start time
intervals.sort(key=lambda x: x[0])
# Min heap to track end times of ongoing meetings
heap = []
for interval in intervals:
# If earliest ending meeting has ended, reuse that room
if heap and heap[0] <= interval[0]:
heapq.heappop(heap)
# Add current meeting's end time
heapq.heappush(heap, interval[1])
# Size of heap = number of rooms needed
return len(heap)
Time Complexity: O(n log n) - Sorting + heap operations
Space Complexity: O(n) - Heap storage
Key Concepts:
- Heap optimization: Track only end times of active meetings
- Room reuse: If earliest meeting ends before new one starts, reuse room
- Heap size: Represents number of simultaneous meetings
- Cleaner logic: More intuitive than two-pointer approach
Core Algorithm Patterns from Today's Problems
1. Sort-First Pattern (Primary Pattern)
When to use:
- Intervals are not sorted
- Need to process intervals in chronological order
- Overlap detection requires ordered processing
Examples: Merge Intervals, Meeting Rooms, Insert Interval
Key characteristics:
- Sort by start time:
intervals.sort(key=lambda x: x[0]) - After sorting, can process in single pass
- Time complexity: O(n log n) for sorting + O(n) for processing
Template:
def interval_problem(intervals):
# Step 1: Sort by start time
intervals.sort(key=lambda x: x[0])
# Step 2: Process in order
result = []
for interval in intervals:
# Process based on problem requirements
if condition:
# Merge, check overlap, etc.
pass
else:
result.append(interval)
return result
2. Three-Phase Insertion Pattern
When to use:
- Inserting interval into sorted list
- Need to merge with overlapping intervals
- Input is already sorted (optimization opportunity)
Example: Insert Interval
Key characteristics:
- Phase 1: Add intervals before new interval
- Phase 2: Merge overlapping intervals
- Phase 3: Add remaining intervals
- O(n) time without sorting
Template:
def insert_interval(intervals, newInterval):
merged = []
i = 0
# Phase 1: Before
while i < len(intervals) and intervals[i][1] < newInterval[0]:
merged.append(intervals[i])
i += 1
# Phase 2: Merge
while i < len(intervals) and intervals[i][0] <= newInterval[1]:
newInterval[0] = min(intervals[i][0], newInterval[0])
newInterval[1] = max(intervals[i][1], newInterval[1])
i += 1
merged.append(newInterval)
# Phase 3: After
while i < len(intervals):
merged.append(intervals[i])
i += 1
return merged
3. Event-Based Counting Pattern
When to use:
- Finding maximum simultaneous events
- Resource allocation problems
- Counting overlaps at specific points
Example: Meeting Rooms II
Key characteristics:
- Separate start and end events
- Track count changes at each event
- Maximum count = answer
- Can use two pointers or priority queue
Template:
def min_resources(intervals):
# Extract and sort events
starts = sorted([i[0] for i in intervals])
ends = sorted([i[1] for i in intervals])
# Two pointers
start_ptr, end_ptr = 0, 0
count = 0
max_count = 0
while start_ptr < len(intervals):
if starts[start_ptr] < ends[end_ptr]:
count += 1
start_ptr += 1
else:
count -= 1
end_ptr += 1
max_count = max(max_count, count)
return max_count
4. Adjacent Comparison Pattern
When to use:
- Checking if all intervals are non-overlapping
- Simple conflict detection
- Single pass after sorting
Example: Meeting Rooms
Key characteristics:
- Sort first
- Compare only adjacent intervals
- Early termination on first conflict
- O(n log n) time, O(1) space
Template:
def no_conflicts(intervals):
intervals.sort(key=lambda x: x[0])
for i in range(1, len(intervals)):
if intervals[i-1][1] > intervals[i][0]:
return False
return True
Performance Optimization Tips
1. Leverage Sorted Input
# If input is already sorted, skip sorting step
def insert_interval_optimized(intervals, newInterval):
# Input is sorted, so we can process directly
# O(n) time instead of O(n log n)
merged = []
i = 0
# ... rest of three-phase logic
Key insight: Always check if input is sorted before sorting.
2. Early Termination
# For conflict detection, return immediately
def can_attend_meetings(intervals):
intervals.sort(key=lambda x: x[0])
for i in range(1, len(intervals)):
if intervals[i-1][1] > intervals[i][0]:
return False # Early exit on first conflict
return True
Key insight: Stop processing as soon as answer is determined.
3. In-Place Merging
# Modify existing array instead of creating new one
def merge_intervals_inplace(intervals):
intervals.sort(key=lambda x: x[0])
i = 0
while i < len(intervals) - 1:
if intervals[i][1] >= intervals[i+1][0]:
intervals[i][1] = max(intervals[i][1], intervals[i+1][1])
intervals.pop(i+1)
else:
i += 1
return intervals
Key insight: Modify in place when possible to save space.
4. Heap vs Two Pointers for Meeting Rooms II
# Two pointers: O(n log n) time, O(n) space
def min_meeting_rooms_two_pointers(intervals):
starts = sorted([i[0] for i in intervals])
ends = sorted([i[1] for i in intervals])
# ... two pointer logic
# Heap: O(n log n) time, O(n) space, but cleaner
def min_meeting_rooms_heap(intervals):
intervals.sort(key=lambda x: x[0])
heap = []
# ... heap logic
Key insight: Heap approach is more intuitive and easier to understand.
Common Pitfalls and Solutions
1. Incorrect Overlap Detection
# Wrong: Only checking if starts overlap
if interval1[0] <= interval2[0] <= interval1[1]:
# This misses cases where interval2 completely contains interval1
# Correct: Check both boundaries
if interval1[0] <= interval2[1] and interval2[0] <= interval1[1]:
# Or simpler after sorting:
if merged[-1][1] >= interval[0]: # For sorted intervals
2. Forgetting to Sort
# Wrong: Trying to merge without sorting
def merge_intervals_wrong(intervals):
merged = [intervals[0]]
for interval in intervals[1:]:
# This won't work correctly if intervals aren't sorted
if merged[-1][1] >= interval[0]:
merged[-1][1] = max(merged[-1][1], interval[1])
# Correct: Always sort first
def merge_intervals_correct(intervals):
intervals.sort(key=lambda x: x[0]) # Essential step!
merged = [intervals[0]]
# ... rest of logic
3. Off-by-One in Merge Condition
# Wrong: Using strict inequality
if merged[-1][1] > interval[0]: # Misses touching intervals
# [1,2] and [2,3] should merge, but this condition fails
# Correct: Use >= for inclusive overlap
if merged[-1][1] >= interval[0]: # Handles touching intervals
# [1,2] and [2,3] correctly merge to [1,3]
4. Not Updating Maximum in Meeting Rooms II
# Wrong: Only tracking final count
count = 0
while p1 < len(intervals):
if start[p1] < end[p2]:
count += 1
else:
count -= 1
p1 += 1
return count # Wrong: This is final count, not maximum
# Correct: Track maximum during processing
count = 0
max_count = 0
while p1 < len(intervals):
if start[p1] < end[p2]:
count += 1
else:
count -= 1
max_count = max(max_count, count) # Track maximum
# ... update pointers
return max_count
5. Incorrect Merge Operation
# Wrong: Only updating end time
merged[-1][1] = interval[1] # Doesn't handle case where merged interval extends further
# Correct: Take maximum of both end times
merged[-1][1] = max(interval[1], merged[-1][1]) # Ensures full coverage
Time Complexity Comparison
Problem Complexity Summary
| Problem | Brute Force | Optimized | Space | Technique |
|---|---|---|---|---|
| Merge Intervals | O(n²) | O(n log n) | O(n) | Sort + Merge |
| Meeting Rooms | O(n²) | O(n log n) | O(1) | Sort + Adjacent Check |
| Insert Interval | O(n log n) | O(n) | O(n) | Three-Phase Insert |
| Meeting Rooms II | O(n²) | O(n log n) | O(n) | Sort + Two Pointers/Heap |
Why Sorting is Essential
Without sorting:
- Need to check all pairs: O(n²) comparisons
- Cannot guarantee processing order
- Complex overlap detection logic
With sorting:
- Single pass processing: O(n) after sort
- Guaranteed chronological order
- Simple adjacent comparisons
- Trade-off: O(n log n) sorting cost, but enables O(n) processing
Conclusion
Interval problems are a crucial category in algorithmic problem-solving, appearing frequently in scheduling, calendar, and resource allocation applications. Key takeaways:
Core Concepts Mastered:
Interval Fundamentals:
- Overlap detection:
a ≤ dandc ≤ bormax(a, c) ≤ min(b, d) - Merging operation:
[min(a, c), max(b, d)]combines overlapping intervals - Sorting prerequisite: Most interval problems require sorting by start time
- Chronological processing: Enables single-pass solutions
Problem Types:
- Merge intervals: Combine overlapping ranges
- Conflict detection: Check if intervals can coexist
- Insertion: Add new interval with merging
- Resource allocation: Find minimum resources needed
Essential Patterns Mastered Today:
Pattern 1: Sort-first approach (Merge Intervals, Meeting Rooms)
Pattern 2: Three-phase insertion (Insert Interval)
Pattern 3: Event-based counting (Meeting Rooms II)
Pattern 4: Adjacent comparison (Meeting Rooms)
Problem-Solving Strategies:
- Always sort by start time for unsorted interval problems
- Leverage sorted input to avoid unnecessary sorting
- Use three-phase approach for interval insertion
- Track maximum count for resource allocation problems
- Early termination when conflict detected
- Choose appropriate data structure (heap vs two pointers)
Optimization Principles:
- Sort to enable linear processing - O(n log n) sort enables O(n) solution
- Process in chronological order - Simplifies overlap detection
- Use event-based approach - For counting simultaneous events
- Track maximum during processing - Not just final state
- Merge correctly - Use
max()for end times to ensure full coverage
Next Steps:
Building on interval fundamentals, future topics will cover:
- Binary Search (can optimize interval search)
- Sweep Line Algorithm (advanced interval problems)
- Segment Trees (range queries and updates)
- Greedy Algorithms (interval scheduling variations)
The problems covered here represent fundamental patterns that appear across technical interviews and real-world scheduling systems. Master these concepts, and you'll have a solid foundation for tackling more complex interval and scheduling challenges.
Practice Problems for Mastery:
Basic Interval Problems:
- 56. Merge Intervals
-
- Insert Interval
-
- Meeting Rooms
-
- Meeting Rooms II
-
- Non-overlapping Intervals
-
- Minimum Number of Arrows to Burst Balloons
Advanced Interval Problems:
- 759. Employee Free Time
-
- Interval List Intersections
-
- Remove Interval
-
- Remove Covered Intervals
-
- Minimum Interval to Include Each Query
References:
- NeetCode - Algorithms
- LeetCode Patterns
- Greg Hogg DSA Algorithm YouTube Channel
- Algorithm Design Manual by Steven Skiena
- Elements of Programming Interviews
This comprehensive guide combines theoretical understanding with practical problem-solving, providing a solid foundation for mastering interval manipulation and scheduling algorithms.