Merge Intervals
1. Merging Overlapping Intervals
| Step | Operation | Condition | Action |
|---|---|---|---|
| 1. Sort | Sort intervals by start time | intervals.sort((a,b) => a[0] - b[0]) |
Ensures sequential processing |
| 2. Initialize | Start with first interval | merged = [intervals[0]] |
Base case for merging |
| 3. Check Overlap | Compare end with next start | if (last.end >= current.start) |
Intervals overlap or touch |
| 4. Merge or Add | Extend end or add new interval | last.end = max(last.end, current.end) |
Update merged result |
Example: Merge overlapping intervals
function merge(intervals) {
if (intervals.length <= 1) return intervals;
// Sort by start time
intervals.sort((a, b) => a[0] - b[0]);
const merged = [intervals[0]];
for (let i = 1; i < intervals.length; i++) {
const current = intervals[i];
const last = merged[merged.length - 1];
if (last[1] >= current[0]) {
// Overlapping - merge by extending end
last[1] = Math.max(last[1], current[1]);
} else {
// No overlap - add as new interval
merged.push(current);
}
}
return merged;
}
// [[1,3],[2,6],[8,10],[15,18]] -> [[1,6],[8,10],[15,18]]
// Time: O(n log n), Space: O(n)
Note: When merging, use
Math.max(last[1], current[1]) for the end time because current interval might be completely contained within the last interval.
2. Insert Interval Optimization
| Phase | Strategy | Condition | Complexity |
|---|---|---|---|
| Before Insert | Add intervals ending before new interval | interval[1] < newInterval[0] |
No overlap, add as-is |
| Merge Phase | Merge all overlapping intervals | interval[0] <= newInterval[1] |
Update start/end bounds |
| After Insert | Add remaining intervals | interval[0] > newInterval[1] |
No more overlaps |
| Optimization | No sorting needed - already sorted | Input guaranteed sorted | O(n) linear time |
Example: Insert interval into sorted intervals
function insert(intervals, newInterval) {
const result = [];
let i = 0;
// Phase 1: Add all intervals before newInterval
while (i < intervals.length && intervals[i][1] < newInterval[0]) {
result.push(intervals[i]);
i++;
}
// Phase 2: Merge overlapping intervals
while (i < intervals.length && 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.push(newInterval);
// Phase 3: Add remaining intervals
while (i < intervals.length) {
result.push(intervals[i]);
i++;
}
return result;
}
// intervals = [[1,3],[6,9]], newInterval = [2,5]
// Output: [[1,5],[6,9]]
3. Interval Intersection Logic
| Relationship | Condition | Intersection | Visual |
|---|---|---|---|
| Overlap Exists | max(start1, start2) <= min(end1, end2) |
[max(start1,start2), min(end1,end2)] |
Intervals share common region |
| No Overlap | end1 < start2 || end2 < start1 |
Empty intersection | Completely separate intervals |
| One Contains Other | start1 <= start2 && end1 >= end2 |
Smaller interval is intersection | Nested intervals |
| Partial Overlap | Start of one < end of other | Overlapping region only | Crossing intervals |
Example: Interval list intersections (two sorted lists)
function intervalIntersection(firstList, secondList) {
const result = [];
let i = 0, j = 0;
while (i < firstList.length && j < secondList.length) {
const [start1, end1] = firstList[i];
const [start2, end2] = secondList[j];
// Check if intervals overlap
const start = Math.max(start1, start2);
const end = Math.min(end1, end2);
if (start <= end) {
// Overlap exists
result.push([start, end]);
}
// Move pointer of interval that ends first
if (end1 < end2) {
i++;
} else {
j++;
}
}
return result;
}
// A = [[0,2],[5,10],[13,23],[24,25]]
// B = [[1,5],[8,12],[15,24],[25,26]]
// Output: [[1,2],[5,5],[8,10],[15,23],[24,24],[25,25]]
4. Meeting Rooms Scheduling
| Problem | Approach | Data Structure | Complexity |
|---|---|---|---|
| Can Attend All? | Sort by start, check if any overlap | Sorted array | O(n log n) |
| Min Meeting Rooms | Track concurrent meetings with heap | Min heap of end times | O(n log n) |
| Alternative: Sweep Line | Count +1 at start, -1 at end | Event array with timestamps | O(n log n) |
| Max Concurrent | Track maximum rooms in use at any time | Running count of active meetings | Peak value during sweep |
Example: Can attend all meetings?
function canAttendMeetings(intervals) {
if (intervals.length <= 1) return true;
// Sort by start time
intervals.sort((a, b) => a[0] - b[0]);
// Check for any overlap
for (let i = 1; i < intervals.length; i++) {
if (intervals[i][0] < intervals[i-1][1]) {
return false; // Overlap found
}
}
return true;
}
// [[0,30],[5,10],[15,20]] -> false
// [[7,10],[2,4]] -> true
Example: Minimum meeting rooms needed
function minMeetingRooms(intervals) {
if (intervals.length === 0) return 0;
const starts = intervals.map(i => i[0]).sort((a,b) => a-b);
const ends = intervals.map(i => i[1]).sort((a,b) => a-b);
let rooms = 0;
let maxRooms = 0;
let startPtr = 0, endPtr = 0;
while (startPtr < intervals.length) {
if (starts[startPtr] < ends[endPtr]) {
rooms++; // Meeting starts
maxRooms = Math.max(maxRooms, rooms);
startPtr++;
} else {
rooms--; // Meeting ends
endPtr++;
}
}
return maxRooms;
}
Example: Meeting rooms II with min heap (alternative)
function minMeetingRoomsHeap(intervals) {
if (intervals.length === 0) return 0;
// Sort by start time
intervals.sort((a, b) => a[0] - b[0]);
// Min heap to track end times of ongoing meetings
const endTimes = [intervals[0][1]];
for (let i = 1; i < intervals.length; i++) {
const [start, end] = intervals[i];
// If earliest ending meeting finished, reuse room
if (endTimes[0] <= start) {
endTimes.shift(); // Remove earliest end time
}
// Add current meeting's end time
endTimes.push(end);
endTimes.sort((a, b) => a - b); // Maintain min heap
}
return endTimes.length; // Number of rooms needed
}
// [[0,30],[5,10],[15,20]] -> 2
5. Non-overlapping Intervals Greedy
| Strategy | Sort Key | Greedy Choice | Rationale |
|---|---|---|---|
| Max Non-overlapping | Sort by end time | Always pick interval ending earliest | Leaves most room for future intervals |
| Min Removals | Sort by end time | Keep intervals that end early | Removals = total - max non-overlapping |
| Interval Partitioning | Sort by start time | Assign to earliest available partition | Minimize number of partitions needed |
| Activity Selection | Sort by end time | Greedy end-time selection | Classic greedy algorithm proof |
Example: Minimum number of intervals to remove
function eraseOverlapIntervals(intervals) {
if (intervals.length <= 1) return 0;
// Sort by end time (greedy choice)
intervals.sort((a, b) => a[1] - b[1]);
let count = 0;
let end = intervals[0][1];
for (let i = 1; i < intervals.length; i++) {
if (intervals[i][0] < end) {
// Overlaps with previous - remove this one
count++;
} else {
// No overlap - update end time
end = intervals[i][1];
}
}
return count;
}
// [[1,2],[2,3],[3,4],[1,3]] -> 1 (remove [1,3])
// Greedy: Keep intervals ending earliest
Example: Maximum non-overlapping intervals (activity selection)
function maxNonOverlapping(intervals) {
if (intervals.length === 0) return 0;
// Sort by end time
intervals.sort((a, b) => a[1] - b[1]);
let count = 1; // First interval always included
let end = intervals[0][1];
for (let i = 1; i < intervals.length; i++) {
if (intervals[i][0] >= end) {
// No overlap - include this interval
count++;
end = intervals[i][1];
}
}
return count;
}
// Proof: Greedy choice optimal - interval ending earliest
// leaves maximum space for remaining intervals
6. Timeline Event Processing
| Pattern | Event Types | Processing Order | Use Case |
|---|---|---|---|
| Start/End Events | +1 for start, -1 for end | Sort by time, then type (end before start) | Count active intervals at any time |
| Sweep Line | Process events in chronological order | Maintain running state across timeline | Skyline problem, interval coverage |
| Employee Free Time | Flatten all schedules, find gaps | Merge busy intervals, return gaps | Common free time across people |
| Point vs Interval | Events at specific times | Discrete events vs continuous ranges | Different collision detection logic |
Example: Employee free time (flatten and find gaps)
function employeeFreeTime(schedule) {
// Flatten all intervals
const allIntervals = [];
for (let employee of schedule) {
allIntervals.push(...employee);
}
// Sort by start time
allIntervals.sort((a, b) => a[0] - b[0]);
// Merge overlapping intervals
const merged = [allIntervals[0]];
for (let i = 1; i < allIntervals.length; i++) {
const last = merged[merged.length - 1];
const curr = allIntervals[i];
if (last[1] >= curr[0]) {
last[1] = Math.max(last[1], curr[1]);
} else {
merged.push(curr);
}
}
// Find gaps between merged intervals
const freeTime = [];
for (let i = 1; i < merged.length; i++) {
freeTime.push([merged[i-1][1], merged[i][0]]);
}
return freeTime;
}
// schedule = [[[1,3],[4,6]], [[2,4]], [[6,8]]]
// Output: [[4,6]] is not available, [[6,6]] is gap -> No free time
// Actual output: [] (no common free time)
Example: Sweep line for maximum concurrent events
function maxConcurrentEvents(intervals) {
const events = [];
// Create start and end events
for (let [start, end] of intervals) {
events.push([start, 1]); // Start event
events.push([end, -1]); // End event
}
// Sort events: by time, end events before start events
events.sort((a, b) => {
if (a[0] !== b[0]) return a[0] - b[0];
return a[1] - b[1]; // End (-1) before start (1)
});
let current = 0;
let maxConcurrent = 0;
for (let [time, type] of events) {
current += type;
maxConcurrent = Math.max(maxConcurrent, current);
}
return maxConcurrent;
}
// [[0,30],[5,10],[15,20]] -> 2 concurrent meetings max
Merge Intervals Pattern Summary
- Standard Merge: Sort by start, merge overlapping by extending end
- Insert Interval: Three phases - before, merge, after (O(n) no sort)
- Intersection:
max(start1,start2) <= min(end1,end2) - Meeting Rooms: Min heap of end times or sweep line with events
- Greedy Selection: Sort by end time, pick earliest ending
- Sweep Line: Process start/end events chronologically
Note: When sorting intervals for merging, always sort by start time. For greedy maximum selection problems, sort by end time. For sweep line, process events in chronological order with tie-breaking rules (end before start at same time).
Warning: Be careful with boundary conditions: use
< vs <= consistently. For example, [1,2] and [2,3] might be considered overlapping or adjacent depending on problem definition. Always clarify if endpoints are inclusive or exclusive.