本文属于「征服LeetCode」系列文章之一,这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁,本系列将至少持续到刷完所有无锁题之日为止;由于LeetCode还在不断地创建新题,本系列的终止日期可能是永远。在这一系列刷题文章中,我不仅会讲解多种解题思路及其优化,还会用多种编程语言实现题解,涉及到通用解法时更将归纳总结出相应的算法模板。
为了方便在PC上运行调试、分享代码文件,我还建立了相关的仓库。在这一仓库中,你不仅可以看到LeetCode原题链接、题解代码、题解文章链接、同类题目归纳、通用解法总结等,还可以看到原题出现频率和相关企业等重要信息。如果有其他优选题解,还可以一同分享给他人。
由于本系列文章的内容随时可能发生更新变动,欢迎关注和收藏征服LeetCode系列文章目录一文以作备忘。
这里有 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 <= 10^41 <= durationi, lastDayi <= 10^4这个题和他左右两个邻居来自同一场周赛,而且这道题只是第二题…… 当时美服共有超过2000人参赛,除了前112名,都是一题选手……
这道题很有意义,是一道经典贪心运用题——题目要求我们构造一种可行的课程排列,排列中每门课程的实际结束时间「不超过最晚截止时间 lastDayi」,求最长可行排列的长度。
对于两门课 ( t 1 , d 1 ) (t_1, d_1) (t1,d1) 和 ( t 2 , d 2 ) (t_2, d_2) (t2,d2) ,如果后者的关闭时间较晚,即 d 1 ≤ d 2 d_1 \leq d_2 d1≤d2 ,那么我们先学习前者,再学习后者,总是最优的。这是因为:
因此,我们可以将所有的课程按照关闭时间 d d d 进行升序排序,再依次挑选课程并按照顺序进行学习。
在遍历的过程中,假设我们当前遍历到了第
i
i
i 门课
(
t
i
,
d
i
)
(t_i, d_i)
(ti,di) ,而在前
i
−
1
i-1
i−1 门课程中我们选择了
k
k
k 门课
(
t
x
1
,
d
x
1
)
,
(
t
x
2
,
d
x
2
)
,
⋯
,
(
t
x
k
,
d
x
k
)
(t_{x_1}, d_{x_1}), (t_{x_2}, d_{x_2}), \cdots, (t_{x_k}, d_{x_k})
(tx1,dx1),(tx2,dx2),⋯,(txk,dxk) ,满足
x
1
<
x
2
<
⋯
<
x
k
x_1 < x_2 < \cdots < x_k
x1<x2<⋯<xk ,那么有:
{
t
x
1
≤
d
x
1
t
x
1
+
t
x
2
≤
d
x
2
⋯
t
x
1
+
t
x
2
+
⋯
+
t
x
k
≤
d
x
k
{tx1≤dx1tx1+tx2≤dx2⋯tx1+tx2+⋯+txk≤dxk
如果上述选择方案是前 i − 1 i-1 i−1 门课程的「最优方案」:即不存在能选择 k + 1 k+1 k+1 门课程的方法,也不存在能选择 k k k 门课程,并且总时长更短(小于 t x 1 + t x 2 + ⋯ + t x k t_{x_1} + t_{x_2} + \cdots + t_{x_k} tx1+tx2+⋯+txk )的方案,那么我们可以基于该方案与第 i i i 门课程 ( t i , d i ) (t_i, d_i) (ti,di) ,构造出前 i i i 门课程的最优方案:
这就说明我们可以将第 x j x_j xj 门课程替换成第 i i i 门课程。这样一来,当我们遍历完所有的 n n n 门课程后,就可以得到最终的答案。
我们需要一个数据结构支持「取出 t t t 值最大的那门课程」,因此我们可以使用优先队列(大根堆)。我们依次遍历每一门课程,当遍历到 ( t i , d i ) (t_i, d_i) (ti,di) 时:
在遍历完成后,优先队列中包含的元素个数即为答案。
class Solution {
public:
int scheduleCourse(vector<vector<int>>& courses) {
sort(courses.begin(), courses.end(), [](const auto &c0, const auto &c1) {
return c0[1] < c1[1];
});
priority_queue<int> q;
// 优先队列中所有课程的总时间
int total = 0;
for (const auto &course : courses) {
int ti = course[0], di = course[1];
if (total + ti <= di) {
total += ti;
q.push(ti);
} else if (!q.empty() && q.top() > ti) {
total -= q.top() - ti;
q.pop();
q.push(ti);
}
}
return q.size();
}
};
复杂度分析: