参考博文:https://blog.csdn.net/kiritow/article/details/52208488
题目描述
有N项工作,每项工作分别在Si时间开始,在Ti时间结束。对于每项工作,你都可以选择参与与否。如果选择了参与,那么自始至终都必须全程参与。此外,参与工作的时间段不能重叠(即使是开始的瞬间和结束的瞬间重叠也是不允许的)。
目标是尽可能参与可能多的工作,那么最多能参与多少项工作?
样例输入
n = 5, s= {1,2,4,6,8}, t={3,5,7,9,10}
样例输出
3 (选取工作1、3、5)
题目分析
作为一个正常人,我们不太可能一眼就想到用贪心算法来解决这个问题,这里的贪心用法是在多次优化后得出的。
首先想到的一般是得到一个解空间,通过暴力解法得到所有的可能,再逐一的去验证,在符合的方案中找到一个最优的方案。
最优化问题都可以通过某种搜索获得最优解,最多区间调度问题也不例外。该问题无非就是选择几个不重叠的区间而已,看看最多能选择多少个,其解空间为一棵二叉子集树,某个区间选或者不选构成了两个分支,如图四所示。我们的目标就是遍历这棵子集树,然后看从根节点到叶节点的不重叠区间的最大个数为多少。可以看出,该问题的解就是n位二进制的某个0/1组合。子集树共有2^n种组合,每种组合都需要判断是否存在重叠区间,如果不重叠则获得1的个数。
上述的例子一共八种组合:
- a
- ab
- ac
- ad
- abc
- abd
- acd
- abcd
现在我们已经得到解空间了,在不进行如何优化的前提下暴力求解。此时如果abcd不是有序排列的区间,则每种组合判断是否有重叠区间的复杂度为O(n^2):{为了确保所有的区间都不覆盖,a应与bcd分别比较共3次,b再与cd分别比较共2次,c再与d比较共1次。数学表达即 (n-1)+(n-2)+(n-3)……+1=1/2(n^2-n)==>即每种组合检验是否有重叠区间的复杂度为O(n^2)}
此时该算法的复杂度为2^n*O(n^2),即O(2^n*n^2),是十分不理想的,现在进行第一次优化。
先将所有区间排个序(按开始时间或者结束时间)
这样排完序后,判断区间是否重叠的算法马上就降到O(n)了。
然后让我们进一步思考,我们能不能优化解空间呢?此处我们可以用贪心的解法,每一步都选择最优的,这样就可以把O(2^n)压缩到O(1)。这个方法实在是巧妙,证明不会,就举个反例吧!
如果按开始时间排:
这个策略显然不行,结果只有a一个。
如果按结束时间排:
这个策略可以的,结果为3个。想法为尽可能先选最早结束的,这样才能选到最多的任务。
证明:https://www.cnblogs.com/ordili/p/8495998.html
代码
package POJ.动态规划与贪心算法;
import java.util.Arrays;
public class Section {
public static void main(String[] args) {
int count=1;//最多可参与的工作数
Task[] t=new Task[] {new Task(1, 3),new Task(8,10),
new Task(4,7),new Task(6,9),new Task(2,5)};
Arrays.sort(t);
for(int i=0;i<t.length;++i) {
System.out.println(t[i]);
}
int end=0;
for(int i=1;i<t.length;++i) {
if(t[end].et<t[i].st) {
count+=1;
end=i;
}
}
System.out.println(count);
}
}
class Task implements Comparable<Task>{
int st;
int et;
public Task(int st, int et) {
super();
this.st = st;
this.et = et;
}
@Override
public int compareTo(Task o) {
return this.et-o.et;
}
@Override
public String toString() {
return st+","+et;
}
}