Key Algorithmic Techniques for Intervals
Sorting by Start-time and End-time
Sorting is a common pre-processing step that simplifies many interval problems.
- Sort by start-time ⇒ useful for merging intervals or checking overlaps
- Sort by end-time ⇒ useful for greedy scheduling problems
Merging Overlapping Intervals
If two intervals overlap, merge them into one.
- Use a sorted list of intervals
- Iterate through intervals and merge if they overlap
vector<vector<int>> merge(vector<vector<int>>& intervals) {
vector<vector<int>> result;
sort(intervals.begin(), intervals.end()); // sort by start time
for(const auto& interval : intervals) {
if(result.empty()
|| result.back()[1] < interval[0]) {
result.push_back(interval); // no overlap, add new interval
}
else {
result.back()[1] = max(result.back()[1], interval[1]); // merge the overlapping intervals
}
}
return result;
}
Use a Min-Heap (Priority Queue) for Scheduling Problems
- a priority queue is useful when we need to track end times of ongoing intervals
- use in problems like Meeting Rooms or CPU Task scheduling
int minMeetingRooms(vector<vector<int>>& intervals) {
sort(intervals.begin(), intervals.end()); // Sort by start time
priority_queue<int, vector<int>, greater<int>> pq; // Min-heap for end times
for (auto& interval : intervals) {
if (!pq.empty() && pq.top() <= interval[0]) {
pq.pop(); // Remove the finished meeting
}
pq.push(interval[1]); // Add the new meeting end time
}
return pq.size(); // Number of active meetings
}
Greedy Approach for Non-Overlapping Intervals
When asked to remove the fewest number of intervals to make them non-overlapping
- sort by end time (instead of start time)
- always keep the earliest finishing interval to leave room for others
int eraseOverlapIntervals(vector<vector<int>>& intervals) {
sort(intervals.begin(), intervals.end(), [](auto& a, auto& b) {
return a[1] < b[1]; // Sort by end time
});
int end = intervals[0][1], count = 0;
for (int i = 1; i < intervals.size(); i++) {
if (intervals[i][0] < end) count++; // Overlap found, remove it
else end = intervals[i][1]; // Update end to the current interval
}
return count;
}
Sweep Line Algorithm + Event Sorting
Useful for interval problems where we need to track :
- how many intervals are active at a given point
- the maximum number of overlapping intervals
Approach:
- convert intervals into events
(start, +1)
and(end, -1)
- sort events: if two events have the same time, process end event first
- keep a running count
int maxEvents(vector<vector<int>>& events) {
vector<pair<int, int>> timeline;
for (auto& e : events) {
timeline.emplace_back(e[0], 1); // Start of event
timeline.emplace_back(e[1], -1); // End of event
}
sort(timeline.begin(), timeline.end()); // Sort by time
int maxAttend = 0, ongoing = 0;
for (auto& [time, change] : timeline) {
ongoing += change;
maxAttend = max(maxAttend, ongoing);
}
return maxAttend;
}
Binary Search for Efficient Interval Lookup
If we need to quickly find an interval, we can use binary search (`lower_bound / upper_bound) Works well when searching for next available intervals
vector<int> findRightInterval(vector<vector<int>>& intervals) {
map<int, int> starts; // Stores {start, index}
for (int i = 0; i < intervals.size(); i++) starts[intervals[i][0]] = i;
vector<int> res;
for (auto& interval : intervals) {
auto it = starts.lower_bound(interval[1]); // Find the next interval
res.push_back(it == starts.end() ? -1 : it->second);
}
return res;
}