本文涉及知识点
贪心 反悔堆 优先队列 临项交换
Leetcode630. 课程表 III
这里有 n 门不同的在线课程,按从 1 到 n 编号。给你一个数组 courses ,其中 courses[i] = [durationi, lastDayi] 表示第 i 门课将会 持续 上 durationi 天课,并且必须在不晚于 lastDayi 的时候完成。
你的学期从第 1 天开始。且不能同时修读两门及两门以上的课程。
返回你最多可以修读的课程数目。
示例 1:
输入:courses = [[100, 200], [200, 1300], [1000, 1250], [2000, 3200]]
输出:3
解释:
这里一共有 4 门课程,但是你最多可以修 3 门:
首先,修第 1 门课,耗费 100 天,在第 100 天完成,在第 101 天开始下门课。
第二,修第 3 门课,耗费 1000 天,在第 1100 天完成,在第 1101 天开始下门课程。
第三,修第 2 门课,耗时 200 天,在第 1300 天完成。
第 4 门课现在不能修,因为将会在第 3300 天完成它,这已经超出了关闭日期。
示例 2:
输入:courses = [[1,2]]
输出:1
示例 3:
输入:courses = [[3,2],[4,3]]
输出:0
提示:
1 <= courses.length <= 104
1 <= durationi, lastDayi <= 104
临项交换+反证法+决策包容性
证明过程比较复杂,结论如下:
性质一:只需要按结束时间升序枚举。
性质二:按性质一排序后,前i门课的最佳解:a,学的课程多更优。 b,相同课程结束时间早的更优。
利用临项交换证明性质一
令最优解,按顺序为:{{t1,d1},{t2,d2} ⋯ \cdots ⋯ {tk,dk}}
∀ \forall ∀i ,如果di < d{i+1} ,则调整两者的顺序。不失一般性,假定i为1。
令调整前: 第x天开始执行{t1,d1},则说明:
x + t1 -1 <= d1 式子一
x + t1 + t2 -1 <= d2 式子二
式子二,结合t1 >=1 → \rightarrow → x + t2 -1 <= d2
式子二结合假定d2 < d1 → \rightarrow → x +t1 + t2 -1 <= d1
即调整后也合法。
所有相邻的两项调整后,di升序。故:我们枚举的时候只需要考虑di升序。
反证法和决策包容性证明性质二
最佳结果:
学的课程多优于学的课程少。
学的课程相等,所用时间短的更优,早结束。
那么推导第i+1门课程的过程如下:
如果学完前i门后,能学第i+1门课程,则将第i+1门课加到最佳课程中。
否则,最佳课程中有课程的用时多于第i+1门课且用时最多的课,替换成第i+1门课。
反证法
假定前i门课的最优解是k门课,某个方案只有k-1门课。有如下推论:
一,某方案一定不劣与最优解的k-1门最短的课,否则用最优解的最短k-1门替换某方案。
二,某方案一定不优与最优解的k-1门最短的课,优于最短的k-1门,则优于任意k-1门。我们将最优解的前k-1替换成某方案会更优,与最优解矛盾。如果有重复课程都删除,则删除后,分别有k1和k1-1门课,仍然符合结论。
故:某方案就是最优解最短的k-1门课。
决策包容性
某方案如果不选择第i+1门课,则一定劣与最优解。
如果最优接的后续状态,能够选择k+1门,则最优解一定优于某方案。
余下情况,最优解的后续状态有如下两种可能:
{ 一,最优解的 k 门。 k 门的用时都小于等于第 i + 1 门。 二最优解的 ( k − 1 ) 门 + 第 ( i + 1 ) 门。 o t h e r \begin{cases} 一,最优解的k门。&& k门的用时都小于等于第i+1门。 \\ 二 最优解的(k-1)门+ 第(i+1)门。 && other \\ \end{cases} {一,最优解的k门。二最优解的(k−1)门+第(i+1)门。k门的用时都小于等于第i+1门。other
某方案只有情况二,即:最优解方案包容某方案。
代码
思路
小根堆headEndNeed记录 {ti,d1}
我们处理完前i项课程的最终结果放到:headRes中。use记录最佳结果的总用时。
学习完k+1门课,用是use + need,则学完最后一天的时间为use+need,从1开始。
时间复杂度: O(nlogn) 每门课的操作时间是log(n)。
核心代码
class Solution {
public:int scheduleCourse(vector<vector<int>>& courses) {priority_queue<std::pair<int, int>, vector< std::pair<int, int>>, greater<>> headEndNeed;for (const auto& v : courses) {headEndNeed.emplace(make_pair(v[1], v[0]));}priority_queue<int> headRes;int use = 0;while (headEndNeed.size()) {auto [end, need] = headEndNeed.top();headEndNeed.pop();if (use + need <= end) {headRes.emplace(need);use += need;}else {if (headRes.size() && (headRes.top() > need)) {use += (need - headRes.top());headRes.pop();headRes.emplace(need);}}}return headRes.size();}
};
单元测试
template<class T1, class T2>
void AssertEx(const T1& t1, const T2& t2)
{Assert::AreEqual(t1, t2);
}template<class T>
void AssertEx(const vector<T>& v1, const vector<T>& v2)
{Assert::AreEqual(v1.size(), v2.size());for (int i = 0; i < v1.size(); i++){Assert::AreEqual(v1[i], v2[i]);}
}template<class T>
void AssertV2(vector<vector<T>> vv1, vector<vector<T>> vv2)
{sort(vv1.begin(), vv1.end());sort(vv2.begin(), vv2.end());Assert::AreEqual(vv1.size(), vv2.size());for (int i = 0; i < vv1.size(); i++){AssertEx(vv1[i], vv2[i]);}
}namespace UnitTest
{ vector<vector<int>> courses;TEST_CLASS(UnitTest){public:TEST_METHOD(TestMethod00){courses = { {100, 200}, {200, 1300}, {1000, 1250}, {2000, 3200} };auto res = Solution().scheduleCourse(courses);AssertEx(3, res);}TEST_METHOD(TestMethod01){courses = { {1,2} };auto res = Solution().scheduleCourse(courses);AssertEx(1, res);}TEST_METHOD(TestMethod02){courses = { {3,2},{4,3} };auto res = Solution().scheduleCourse(courses);AssertEx(0, res);}};
}
扩展阅读
视频课程
先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
https://edu.csdn.net/course/detail/38771
如何你想快速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.csdn.net/lecturer/6176
相关推荐
我想对大家说的话 |
---|
《喜缺全书算法册》以原理、正确性证明、总结为主。 |
按类别查阅鄙人的算法文章,请点击《算法与数据汇总》。 |
有效学习:明确的目标 及时的反馈 拉伸区(难度合适) 专注 |
闻缺陷则喜(喜缺)是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。 |
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。 |
如果程序是一条龙,那算法就是他的是睛 |
测试环境
操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 C++17
如无特殊说明,本算法用**C++**实现。