import heapq
class Solution:
def scheduleCourse(self, courses):
# Sort courses by their end day (lastDay)
courses.sort(key=lambda x: x[1])
total_time = 0
max_heap = []
for duration, lastDay in courses:
total_time += duration
heapq.heappush(max_heap, -duration) # use negative for max-heap
if total_time > lastDay:
# Remove the longest course to stay within the deadline
total_time += heapq.heappop(max_heap)
return len(max_heap)
#include <vector>
#include <queue>
#include <algorithm>
using namespace std;
class Solution {
public:
int scheduleCourse(vector<vector<int>>& courses) {
sort(courses.begin(), courses.end(), [](const vector<int>& a, const vector<int>& b) {
return a[1] < b[1];
});
priority_queue<int> maxHeap;
int totalTime = 0;
for (const auto& c : courses) {
totalTime += c[0];
maxHeap.push(c[0]);
if (totalTime > c[1]) {
totalTime -= maxHeap.top();
maxHeap.pop();
}
}
return maxHeap.size();
}
};
import java.util.*;
class Solution {
public int scheduleCourse(int[][] courses) {
Arrays.sort(courses, (a, b) -> a[1] - b[1]);
PriorityQueue<Integer> maxHeap = new PriorityQueue<>(Collections.reverseOrder());
int totalTime = 0;
for (int[] c : courses) {
totalTime += c[0];
maxHeap.add(c[0]);
if (totalTime > c[1]) {
totalTime -= maxHeap.poll();
}
}
return maxHeap.size();
}
}
class MaxHeap {
constructor() {
this.heap = [];
}
push(val) {
this.heap.push(val);
this._siftUp();
}
pop() {
if (this.heap.length === 1) return this.heap.pop();
const max = this.heap[0];
this.heap[0] = this.heap.pop();
this._siftDown();
return max;
}
size() {
return this.heap.length;
}
_siftUp() {
let idx = this.heap.length - 1;
while (idx > 0) {
let parent = Math.floor((idx - 1) / 2);
if (this.heap[parent] >= this.heap[idx]) break;
[this.heap[parent], this.heap[idx]] = [this.heap[idx], this.heap[parent]];
idx = parent;
}
}
_siftDown() {
let idx = 0;
const length = this.heap.length;
while (true) {
let left = 2 * idx + 1, right = 2 * idx + 2, largest = idx;
if (left < length && this.heap[left] > this.heap[largest]) largest = left;
if (right < length && this.heap[right] > this.heap[largest]) largest = right;
if (largest === idx) break;
[this.heap[largest], this.heap[idx]] = [this.heap[idx], this.heap[largest]];
idx = largest;
}
}
}
var scheduleCourse = function(courses) {
courses.sort((a, b) => a[1] - b[1]);
let totalTime = 0;
let maxHeap = new MaxHeap();
for (let [duration, lastDay] of courses) {
totalTime += duration;
maxHeap.push(duration);
if (totalTime > lastDay) {
totalTime -= maxHeap.pop();
}
}
return maxHeap.size();
};
You are given a list of courses, where each course is represented as a pair [duration, lastDay]
:
duration
is the number of days required to complete the courselastDay
is the last day by which the course must be finished
The input is a list of courses
where each element is [duration, lastDay]
. Return the maximum number of courses you can take.
At first glance, this problem seems similar to scheduling or interval problems. One might think of trying every possible order of taking courses, but that would be inefficient since the number of combinations grows rapidly as the number of courses increases.
The naive approach would be to try all possible subsets and orders, but this is computationally infeasible for large inputs.
Instead, we need to find a way to optimize our choices. Since we want to take as many courses as possible, it makes sense to:
lastDay
. This ensures that we always consider the tightest deadlines first, which helps in maximizing the number of courses.
total_time
of the durations of the courses we've chosen so far.
total_time
and push its duration to the max heap.total_time
exceeds the current course's lastDay
, we remove the course with the largest duration from our selection (pop from the max heap) and subtract its duration from total_time
. This step helps us "make room" for more courses by dropping the most time-consuming one.The heap is crucial because it lets us efficiently keep our schedule as tight as possible, always prioritizing shorter courses if we need to make room.
Sample Input: courses = [[100, 200], [200, 1300], [1000, 1250], [2000, 3200]]
[[100, 200], [1000, 1250], [200, 1300], [2000, 3200]]
total_time = 100
total_time = 1100
total_time = 1300
total_time = 3300
total_time = 2300
This process shows how we always make room for more courses by dropping the most time-consuming one when necessary.
O(n!)
(factorial time), which is infeasible for large n
.
O(n \log n)
O(n \log n)
O(n \log n)
O(n)
for the heap (in the worst case, all courses are taken)The use of a heap ensures that both adding and removing courses is efficient, and sorting up front keeps our schedule as tight as possible.
The key insight for Course Schedule III is to always try to fit courses with earlier deadlines first, and to use a max heap to efficiently "swap out" the most time-consuming course when necessary. This greedy approach, combined with efficient data structures, allows us to maximize the number of courses we can attend without violating any deadlines. The elegance of the solution comes from reducing a complex scheduling problem to a simple process of sorting and heap management.