leetcode 630. 课程表 III 贪心
leetcode 630. 课程表 III
题目描述
这里有 n
门不同的在线课程,他们按从 1
到 n
编号。每一门课程有一定的持续上课时间(课程时间)t
以及关闭时间第 d
天。一门课要持续学习 t
天直到第 d
天时要完成,你将会从第 1
天开始。
给出 n
个在线课程用 (t, d)
对表示。你的任务是找出最多可以修几门课。
样例
输入: [[100, 200], [200, 1300], [1000, 1250], [2000, 3200]]
输出: 3
解释:
这里一共有 4 门课程, 但是你最多可以修 3 门:
首先, 修第一门课时, 它要耗费 100 天,你会在第 100 天完成, 在第 101 天准备下门课。
第二, 修第三门课时, 它会耗费 1000 天,所以你将在第 1100 天的时候完成它, 以及在第 1101 天开始准备下门课程。
第三, 修第二门课时, 它会耗时 200 天,所以你将会在第 1300 天时完成它。
第四门课现在不能修,因为你将会在第 3300 天完成它,这已经超出了关闭日期。
注意
- 整数 1 <= d, t, n <= 10000。
- 你不能同时修两门课程。
算法
(贪心,堆) \(O(nlog n)\)
- 首先按照终止时间
d
大小排序。 - 考虑两门课的
(t1, d1)
和(t2, d2)
的情况。- 如果
t1 + t2 <= d1
,则两门课随意安排都可以上。 - 如果
t1 + t2 > d1 && t1 + t2 <= d2
,则上完第一门课后再上第二门课。 - 如果
t1 + t2 > d2
,则两门课只能上一门,这里当然会选择上时间短的那一门,即删除时间最长的一们课。
- 如果
- 推广:
f(i)
表示前i
门课存在最多可以修读的课程数目。- 总上课时间
sum <= di
,第i
门课可以可以加入,f(i) = f(i -1) + 1
- 总上课时间
sum > di
, 需要删除时间最长的课程,f(i) = f(i - 1)
- 总上课时间
- 具体做法:从头开始遍历课程,维护总的上课时间
sum
。- 若
sum
加上当前课的时间小于等于当前课i
的结束时间,则直接将课加入到选课列表中,并记录在大根堆里。 - 若
sum
加上当前课的时间大于当前课i
的结束时间,则考虑大根堆中耗时最长的课top
,如果top
的耗时比当前课程i
要长,则替换为当前课程i
;否则不替换。
- 若
时间复杂度
- 排序时间复杂度为 \(O(n log n)\),枚举课程需要 \(O(n)\),每次访问单调队列需要 \(O(log n)\),故总时间复杂度为 \(O(n log n)\)。
C++ 代码
class Solution {
public:
int f[10010];
priority_queue<int> heap;
int scheduleCourse(vector<vector<int>>& courses) {
sort(courses.begin(), courses.end(), [&](vector<int> &a, vector<int> &b){
return a[1] < b[1];
});
int sum = 0;
for(auto &c : courses){
sum += c[0];
heap.push(c[0]);
if(sum > c[1]){
sum -= heap.top();
heap.pop();
}
}
return heap.size();
}
};
dp超时
class Solution {
public:
int f[10010];
int scheduleCourse(vector<vector<int>>& courses) {
sort(courses.begin(), courses.end(), [&](vector<int> &a, vector<int> &b){
if(a[1] == b[1]) return a[0] < b[0];
else return a[1] < b[1];
});
int n = courses.size();
int res = 0;
for(int i = 0; i < n; i++){
for(int j = courses[i][1]; j >= courses[i][0]; j--){
f[j] = max(f[j], f[j - courses[i][0]] + 1);
res = max(res, f[j]);
}
}
return res;
}
};