Interval scheduling
Interval scheduling is a class of problems where given time intervals have to be arranged in order to maximize or minimize a target function with no two intervals overlapping.
A common instance of the interval scheduling problem asks to find the maximum sum of weights in a set of overlapping intervals. Let’s take the following intervals as an input: each interval has an associate weight/value which renders it more or less preferable compared to another one
\[\begin{array}{c|lcr} n & \text{Interval} & \text{Value} \\ \hline 1 & [0;5] & 10 \\ 2 & [3;8] & 11 \\ 3 & [4;7] & 6 \\ 4 & [9;15] & 20 \\ 5 & [15;17] & 4 \\ \end{array}\]There’s an interesting dynamic programming solution to finding the maximum sum set of non-overlapping intervals. The key observation is that each interval may be added to the previous optimal set or not. Ordering the intervals by their stop
value will help visualize this solution
Every time an interval is picked up for being evaluated (intervals which finish before are chosen first) it can be skipped (no changes in the previous optimal value) or can be chosen. In case it is chosen all the intervals which overlap with this latter one need to be dropped. The condition for which a previous interval has to be dropped is having its stop
value greater than the start
value for the interval being evaluated.
If \( g(i) \) is the maximum sum we can get with the first \( i \) intervals ordered by their stop
values, the problem can be expressed as
with
\[j = max(i) \ s.t. \ j_{stop} \le i_{start}\]#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
struct Interval {
int start;
int stop;
int value;
};
int maximumNonOverlappingValue(vector<Interval>& intervals) {
// Sort intervals by stop value in O(N log N)
sort(intervals.begin(), intervals.end(), [](auto int1, auto int2) {
if (int1.stop < int2.stop)
return true;
else
return false;
});
vector<int> maxWithIntervals(intervals.size() + 1, 0); // 1-based
for (int i = 0; i < intervals.size(); ++i) {
int opt1 = maxWithIntervals[i]; // Do not take this i-th interval
int lo = 0, hi = i;
while (lo <= hi) { // Binary search for the greatest stop value <= [i].start
int mid = static_cast<int>(ceil((lo + hi) / 2.0f));
if (intervals[mid].stop <= intervals[i].start)
lo = mid + 1;
else
hi = mid - 1;
}
// (lo - 1) is the last valid value
// maxWithIntervals is 1-based
int opt2 = maxWithIntervals[lo] + intervals[i].value;
maxWithIntervals[i+1] = max(opt1, opt2);
}
return maxWithIntervals.back();
}
int main() {
vector<Interval> intervals = {
// Start End Value
{ 0, 5, 10 },
{ 3, 8, 11 },
{ 4, 7, 6 },
{ 9, 15, 20 },
{ 15, 17, 4 }
};
cout << maximumNonOverlappingValue(intervals);
return 0;
}
The complexity is \( O(N \log{N}) \) since sorting the intervals takes \( O(N \log{N}) \) and each binary search for the \( j \) intervals is \( O(\log{N}) \).