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