其实和前面的课程表题目不同,这题应该和拓扑排序没啥关系,应该属于贪心算法的区间覆盖问题:数据结构与算法:37 | 贪心算法:贪心算法实现Huffman压缩编码_zj-CSDN博客
区间覆盖问题里,贪心地选左端点跟前面的已经覆盖的区间不重合的(限制条件),右端点又尽量小的。
限制值是截止日期,期望值就是课程的门数,选谁都是一门课嘛,一开始我想的是尽量选择截止日期早的,不过想想不太合理。写出来也确实:
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] ? a[0] < b[0] : a[1] < b[1];
});
int res = 0, sum = 0;
for(const auto &v : courses){
if(sum + v[0] <= v[1]){
++res;
sum += v[0];
}
}
return res;
}
};
在这个用例出错了:
[[5,5],[4,6],[2,6]]
预期 :2,输出:1
按照上面的的策略,优先选择[5,5],然后剩下的两个都不能选了。但是如果我优先选择[2,6]的话,[4,6]也刚好能选,能够修两门课,是更好的选择。
来分析一下为什么会错,因为真正的右端点并不是数组里的v[1],v[1]是截止时间,而不是真正的课程结束时间。比如我一开始选择[2,6],那么这门课程在时间点为0+2 = 2的时候就结束了,此时我就可以选择下一门课了,而不需要等到这门课的截至日期6的时候。
正确思路是,还是之前的排序方法,先按照截至日期排序,这是符合常理的,截至日期更近的,一般需要尽快选择。所以还是上面的那个例子,我们一开始还是选择[5, 5],而当进行第二个选择的时候,下一个数据是[2,6](由于排序的关系,后面的选择截至日期总是更靠后的),这时候我们发现:
- 选了[5, 5]之后,[2, 6]不能选择了(0+5+2 > 6),也就是这两者不能共存。但是我们可以直接抛弃[2, 6]吗?
- 可以看到选择[5, 5]的持续时间5 > 选择[2, 6]的持续时间2,也就是同样是一门课,选择[2, 6]可以更早的结束,那不就意味着为后续的选择留下更多的时间吗?所以[2, 6]是比[5, 5]更好的选择,我们就可以用[2, 6]替换[5, 5]。
所以总体思路就像贪心+动态选择
使用优先级队列方便处理:
class Solution {
public:
struct cmp{
bool operator()(const int a, const int b){//重载函数调用运算符
return a < b;
}
};
int scheduleCourse(vector<vector<int>>& courses) {
sort(courses.begin(), courses.end(), [](const vector<int> &a, const vector<int> &b){
return a[1] == b[1] ? a[0] < b[0] : a[1] < b[1];
});
priority_queue<int, vector<int>, cmp> q;//这样也行
//priority_queue<int, vector<int>, less<int>> q;//默认就是大顶堆,所以用下一行的也行
//priority_queue<int> q;
int sum = 0;
for(const auto &v : courses){
if(sum + v[0] <= v[1]){//可选,并不互斥
sum += v[0];
q.push(v[0]);
}else if(!q.empty() && q.top() > v[0]){//当前v是更好的选择,可以进行替换
sum += v[0] - q.top();
q.pop();
q.push(v[0]);
}
}
return q.size();
}
};