给你一堆需要完成的任务,请问你能按时完成所有任务吗?
提示:华为202204之前的面试题
类似于安排会议,排队洗完,排队做咖啡等问题,考验小根堆的熟练运用
文章目录
题目
给你一个N*2的数组arr,代表N个任务,2维信息分别是任务的起始时间,任务的截止时间,就跟大厂笔试题一样,只能在start时间开始做题,也必须在end时间做完,做不完就不合格,完不成任务。注意,这里每一个任务的完成时间都需要1单位时间,请问你能完成这个N个任务吗?
一、审题
目前有这么几个任务:13,12,35,37,1时刻需要加入2个任务,3时刻需要加入俩任务
我们在遇到需要核实任务有没有完成的截止时间时,就需要核实这个截止时间下,之前的时间够不够解决那些需要完成的任务。
显然,
2时刻是一个截止时间,上次开始做任务起始于1,距离上一次可以做任务的时间差为2-1=1,恰好够做一个任务,完成12
3时刻是一个截止时间,上次开始做任务起始于1,距离上一次可以做任务的时间差为3-2=1,恰好够做一个任务,完成13
5时刻是一个截止时间,上次开始做任务起始于3,距离上一次可以做任务的时间差为5-3=2,完全够做一个任务,完成35
7时刻是一个截止时间,上次开始做任务起始于3,距离上一次可以做任务的时间差为7-4=2,完全够做一个任务,完成37
返回true
示例:看图
那完不成的的例子呢?
上面那个案例,在1时刻,还多一个任务13的话,在3时刻,需要核验,那显然时差只有1的情况下,俩13任务,只能完成一个,根本无法完成第二个任务,所以返回false
现在理解题意了吧!
二、解题
这种题,典型的贪心策略:
咱们需要在可以做任务的时候,尽快把截止时间早的干掉,然后在每一个截止时间那检查一下,所有截止时间之前的任务都全部完成了没有,一旦发现还有任务在截止时间之前必须要完成,但是又完不成,必然false。
首先,我们将时间点按照开始时间,和截止时间来做时刻的序列化,目的是绕开所有t+=1的时刻【这算是舍弃思想 ,加速算法】,咱们只在关键start点假如任务,在截止时间点核验任务是否已经完成?
每一个任务建立2个时间点对象【TimePoint】,属性有仨:time,add,end
一个以start作为时间点time,并标记这个时间点是add任务行为吗?是add即true,否则就是截止时间add=false;
如果是add行为,那这个任务截止时间end就有意义,如果是false,那end就没啥用【end是一个伴随数据】
代码如下:
public static class TimePoint{
public int time;
public int end;//伴随加任务的end时间
public boolean isAdd;
public TimePoint(int t, int e, boolean a){
time = t;
end = e;
isAdd = a;
}
}
然后就是算法大流程:
(1)把起始时间和截止时间都序列化,放入时间点对象
(2)然后遍历整个时间点,在起始时间处加入任务,将这个起始任务的伴随截止时间end放入,小根堆堆顶;
(3)途中,只要遇到截止时间点,清算核验是否完成任务?与上一次的时间间隔有多长,就能完成几个任务,完不成就false;
举例:12,12,13,25
(1)序列化时间对象为8长度时间对象:
1【起始伴随截止时间为3】1【起始伴随2】1【起始伴随3】2【截止】2【起始伴随5】3【截止】3【截止】5【截止】
(2)遍历时间点,前3个1,加入heap,排序为233,然后下一次i遇到2,是一个截止时间,curTime=2,preTime=1,d=1,只需要弹出一个任务,代表完成了一个,2弹出;
i继续增加到2,加入heap为5,然后i继续增加到3,是一个截止时间,curTime=3,preTime= 2,d=1,只需要弹出一个任务,这是这段时间内你只能完成的个数,now发现,heap堆顶的截止时间3<=3,你马上过了3这个时间点,还有截止时间为3的任务,你就完不成任务,false;
看代码:
//按照time排序
public static class timeComparator implements Comparator<TimePoint>{
@Override
public int compare(TimePoint o1, TimePoint o2){
return o1.time - o2.time;//这个时间可能是起始时间,可能是截止时间
}
}
//判断任务可以完成吗?
public static boolean canDoFinishTask(int[][] arr){
if (arr == null || arr.length == 0) return true;
int N = arr.length;
//先变数组对象,再排序--2倍
TimePoint[] time = new TimePoint[N << 1];
for (int i = 0; i < N; i++) {
time[i] = new TimePoint(arr[i][0], arr[i][1], true);//加任务add为true,end有效
time[i + N] = new TimePoint(arr[i][1], arr[i][1], false);//非add,end无效
}
//Arrays.sort(time,(a, b)->a.time - b.time);//这难道又是一种新的玩意?
Arrays.sort(time, new timeComparator());//这难道又是一种新的玩意?
PriorityQueue<Integer> heap = new PriorityQueue<>();
int preTime = time[0].time;//此前的时间点
//核验一遍所有的时间点,是加就让任务的end时间进heap,否则核验
for (int i = 0; i < 2*N; i++) {
if (time[i].isAdd) heap.add(time[i].end);
else {
//否则就是截止时间,加入是不干
int curTime = time[i].time;//这种是截止时间
//与此前的 时间差 就是我们可以完成的任务数
for (int j = preTime; j < curTime; j++) {
heap.poll();//不断地完成截止时间早的任务
if (heap.isEmpty()) break;//没了提前出来
}
//然后看看还有任务竟然比我curTime早吗,早的话就完蛋
if (heap.peek() <= curTime) return false;
preTime = curTime;
}
}
//全部核验完成都没有false
return true;
}
测试:
public static void test(){
int[][] arr = {
{1,3},{1,2},{1,3},{1,4}
};
System.out.println(canDoFinishTask(arr));
}
public static void main(String[] args) {
test();
}
总结
提示:重要经验:
1)理解类似起止时间的对象这种任务,往往是堆来解决
2)贪心策略,多练习,多熟悉;