和之前的两道course schedule不同,这道题和图一点关系也没有。是interval类的题,一般很大可能跟贪婪有关系。这道题要我们schedule course从而可以完成最多的课程,对完成的定义是,这个course要在它的deadline之前完成。我们知道每个课程的长度t和deadline d,我们要决定如何选择这些课程。首先我们假设所有input都是合理的,也就是d >= t,否则这个课程是没法schedule的。那么既然更deadline有关,我们假设先选deadline更早的。但是显然这样没法达到最优解,比如(5,5), (2,6),(4,6),我们先选第一个,但是事实上最优解是选后面两个。那么我们需要修改我们的算法,如果没有空间放了,并且我们发现之前所选的集合中有比当前课程i更长的课程j我们把课程j换出来,把课程i换入集合。这样我们可以让集合的总时间T更短,可以给之后的课程更多的空间,并且也满足条件,因为i的deadline比j的deadline还要晚,我们总时间反而缩短,代表课程i的结束时间(总课程时间)比j在的时候总课程的结束时间还要早,所以仍然满足条件。如何证明这个算法正确性呢。我们在这篇文章中提到过如何证明贪婪算法的正确性,这里我们第二种思路证明。
假设存在解S,C代表所有课程的集合,dd[i]表示课程i的deadline,t[i]代表课程i的长度,假设我们存在一个pair (i, j),dd[j] >= dd[i]并且t[j] <= t[i],把课程i从S的集合换出 把j换入,我们之前证明过依然满足条件,但是这样可以让总时间T缩短。换句话说,给定集合S的大小,我们每remove一个这样的pair,总时间T就会减小,翻过来,每多一个这样的pair总时间T增加,所以当不存在这样的pair时,T是最小的。也就是当最优解O和S的size是一样的话,S的总时间总是小于等于O的,那么如果O之后可以容纳更多的课程,S同理也应该容纳同样多的课程,所以O的size不可能大于S,所以S就是最优解。
为了记录当前S中持续时间最长的课程,我们需要优先队列,时间复杂度O(nlogn),代码如下:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Solution { | |
public: | |
int scheduleCourse(vector<vector<int>>& courses) { | |
int n = courses.size(); | |
sort(courses.begin(), courses.end(), [](const vector<int>& v1, const vector<int>& v2){ return v1[1] < v2[1]; }); | |
int start = 0; | |
priority_queue<int> pq; | |
for(int i = 0; i < n; ++i) | |
{ | |
auto course = courses[i]; | |
if(start + course[0] <= course[1]) | |
{ | |
start += course[0]; | |
pq.push(course[0]); | |
} | |
else if(pq.top() > course[0]) | |
{ | |
start -= (pq.top() - course[0]); | |
pq.pop(); | |
pq.push(course[0]); | |
} | |
} | |
return pq.size(); | |
} | |
}; |
这道题还有另外一种变体,如果我们不是求最多可以上的课程。而是假设我们有n个task,每个task有持续时间和deadline,每一个task如果有delay,delay的时间为完成时间f(i) - dd[i],那么如何schedule task使得总的delay时间最少。这里直觉告诉我们是EDF(earliest deadline first),那么这个算法的正确性,我们需要进一步证明,我们依然采取第二种思路证明。假设解S种存在pair(i, j),f[i] < f[j]并且dd[i] > dd[j],如图所示:
假设我们交换一对pair,首先job j的delay减少了。那么对于job i来说delay增加但是总的delay有没有减少呢?假设交换前job j完成时间为T。交换前job j的delay为T - dd[j],交换后job i的delay为T - dd[i],但是dd[i] > dd[j]所以job i的delay增加了但依然比交换前job j的delay要少,并且交换后job j的delay也减少了,所以总的delay减少。也就是每remove这样一个pair,我们的总delay会减少,那么清楚所有这样的pair之后,delay就是最小的。代码略过,就是简单的按deadline排序。
那么我们从这里也可以看出一道题greedy算法通常是凭直觉来推断出来的,然后再去试图证明这个算法的正确性。一般不可能将这个过程反过来,因为实在是太不好想了。
No comments:
Post a Comment