Skip to content

Commit 762f325

Browse files
feat: Add Weighted Interval Scheduling
1 parent 3502150 commit 762f325

File tree

2 files changed

+171
-0
lines changed

2 files changed

+171
-0
lines changed
198 KB
Binary file not shown.
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
/**
2+
* @file
3+
* @brief Implements the [Weighted Interval Scheduling]
4+
* (https://www.geeksforgeeks.org/dsa/weighted-job-scheduling/) problem.
5+
*
6+
* @details
7+
* The Weighted Interval Scheduling problem is a classic optimization challenge.
8+
* Given a set of intervals, each with a start time, end time, and a profit,
9+
* the goal is to find a subset of non-overlapping intervals that maximizes
10+
* the total profit. This implementation uses dynamic programming combined
11+
* with binary search to achieve an efficient solution.
12+
*
13+
* Example:
14+
* Intervals: [{1, 3, 50}, {4, 6, 70}, {6, 19, 100}, {2, 5, 20}]
15+
* Max Profit: 120 (by choosing {1, 3, 50} and {4, 6, 70})
16+
*
17+
* Constraints & Assumptions:
18+
* - Interval start times, end times, and profits are non-negative integers.
19+
* - The start time of an interval is always less than its end time.
20+
* - An empty set of intervals will result in a profit of 0.
21+
* - Intervals are considered non-overlapping if one ends before or at the same time the other begins.
22+
*
23+
* Time Complexity: O(n log n), dominated by the initial sort of intervals.
24+
* Space Complexity: O(n) for the dynamic programming table.
25+
*
26+
* @author [Soumyadipta-Banerjee](https://github.com/Soumyadipta-Banerjee)
27+
*/
28+
29+
#include <algorithm> /// for std::sort, std::max
30+
#include <cassert> /// for std::assert
31+
#include <iostream> /// for IO operations
32+
#include <vector> /// for std::vector
33+
34+
/**
35+
* @namespace dynamic_programming
36+
* @brief Dynamic Programming algorithms
37+
*/
38+
namespace dynamic_programming {
39+
/**
40+
* @namespace weighted_interval_scheduling
41+
* @brief Functions for the [Weighted Interval Scheduling]
42+
* (https://www.geeksforgeeks.org/dsa/weighted-job-scheduling/) problem.
43+
*/
44+
namespace weighted_interval_scheduling {
45+
/**
46+
* @brief Struct to represent an interval (or job).
47+
*/
48+
struct Interval {
49+
int start, end, profit;
50+
};
51+
52+
/**
53+
* @brief Comparison function to sort intervals based on their end time.
54+
* @param a First interval
55+
* @param b Second interval
56+
* @returns true if interval a's end time is less than b's.
57+
*/
58+
bool compareIntervals(const Interval& a, const Interval& b) {
59+
return a.end < b.end;
60+
}
61+
62+
/**
63+
* @brief Finds the latest non-overlapping interval using binary search.
64+
* This is a helper function for the main DP algorithm.
65+
* @param intervals Sorted vector of intervals.
66+
* @param i Index of the current interval.
67+
* @returns Index of the latest compatible interval, or -1 if none exists.
68+
*/
69+
int findLatestNonOverlapping(const std::vector<Interval>& intervals, int i) {
70+
int low = 0, high = i - 1;
71+
int result = -1;
72+
73+
// Perform binary search to find the latest compatible interval.
74+
while (low <= high) {
75+
int mid = low + (high - low) / 2;
76+
if (intervals[mid].end <= intervals[i].start) {
77+
// This interval is compatible, it could be our answer.
78+
// But we look for a later one in the right half.
79+
result = mid;
80+
low = mid + 1;
81+
} else {
82+
// This interval overlaps, so we search in the left half.
83+
high = mid - 1;
84+
}
85+
}
86+
return result;
87+
}
88+
89+
/**
90+
* @brief Main function to solve the Weighted Interval Scheduling problem.
91+
* @param intervals A vector of intervals. It will be sorted in-place.
92+
* @returns The maximum achievable profit.
93+
*/
94+
int findMaxProfit(std::vector<Interval>& intervals) {
95+
if (intervals.empty()) {
96+
return 0;
97+
}
98+
99+
// Sort intervals based on their end times. This is a prerequisite.
100+
std::sort(intervals.begin(), intervals.end(), compareIntervals);
101+
102+
int n = intervals.size();
103+
// dp[i] will store the maximum profit for the first i+1 intervals.
104+
std::vector<int> dp(n);
105+
// Base case: The max profit for the first interval is its own profit.
106+
dp[0] = intervals[0].profit;
107+
108+
// Fill the DP table from the second interval onwards.
109+
for (int i = 1; i < n; ++i) {
110+
// Option 1: Include the current interval.
111+
// The profit would be its own profit plus the max profit of compatible intervals.
112+
int profit_including_current = intervals[i].profit;
113+
int latest_non_overlapping_idx = findLatestNonOverlapping(intervals, i);
114+
if (latest_non_overlapping_idx != -1) {
115+
profit_including_current += dp[latest_non_overlapping_idx];
116+
}
117+
118+
// Option 2: Exclude the current interval.
119+
// The profit would be the same as the max profit up to the previous interval.
120+
int profit_excluding_current = dp[i - 1];
121+
122+
// The max profit at this stage is the best of the two options.
123+
dp[i] = std::max(profit_including_current, profit_excluding_current);
124+
}
125+
126+
// The last element in the DP table holds the overall maximum profit.
127+
return dp[n - 1];
128+
}
129+
130+
} // namespace weighted_interval_scheduling
131+
} // namespace dynamic_programming
132+
133+
/**
134+
* @brief Test Function to verify the implementation with corrected test cases.
135+
* @return void
136+
*/
137+
static void test() {
138+
// Test Case 1: Standard example from GeeksforGeeks, verified.
139+
std::vector<dynamic_programming::weighted_interval_scheduling::Interval> intervals1 = {
140+
{3, 10, 20}, {1, 2, 50}, {6, 19, 100}, {2, 100, 200}};
141+
assert(dynamic_programming::weighted_interval_scheduling::findMaxProfit(intervals1) == 250);
142+
143+
// Test Case 2: Your original test case, correct expected profit is 150.
144+
std::vector<dynamic_programming::weighted_interval_scheduling::Interval> intervals2 = {
145+
{1, 3, 50}, {2, 5, 20}, {6, 19, 100}, {4, 6, 70}};
146+
assert(dynamic_programming::weighted_interval_scheduling::findMaxProfit(intervals2) == 220);
147+
148+
// Test Case 3: Another verified online example.
149+
std::vector<dynamic_programming::weighted_interval_scheduling::Interval> intervals3 = {
150+
{1, 3, 5}, {2, 5, 6}, {4, 6, 5}, {6, 7, 4}, {5, 8, 11}, {7, 9, 2}};
151+
assert(dynamic_programming::weighted_interval_scheduling::findMaxProfit(intervals3) == 17);
152+
153+
// Test Case 4: Edge case with an empty set of intervals.
154+
std::vector<dynamic_programming::weighted_interval_scheduling::Interval> intervals4 = {};
155+
assert(dynamic_programming::weighted_interval_scheduling::findMaxProfit(intervals4) == 0);
156+
157+
// Test Case 5: Edge case with a single interval.
158+
std::vector<dynamic_programming::weighted_interval_scheduling::Interval> intervals5 = {{1, 100, 55}};
159+
assert(dynamic_programming::weighted_interval_scheduling::findMaxProfit(intervals5) == 55);
160+
161+
std::cout << "All tests passed successfully!\n";
162+
}
163+
164+
/**
165+
* @brief Main function
166+
* @returns 0 on exit
167+
*/
168+
int main() {
169+
test();
170+
return 0;
171+
}

0 commit comments

Comments
 (0)