282场周赛中的第三题,比赛中没做出来,提交基本都是超时,最后半小时想出来要用二分查找但是很多莫名bug,下午看了别人的题解自己做还是很多bug,提交第16次终于通过了,因此记录一下遇到的坑。
需注意的点
1.二分查找开始时确定最大值和最小值
我的想法是:
假设每辆车都用最少的单趟时间,得到最小值:最小时间=旅途总数*最小的单趟时间/车的数量;同理,最大时间=旅途总数*最大的单趟时间/车的数量。
这样做有一个很严重的问题:int型的除法只保留整数,会造成巨大的误差!
(例如:最大时间=199(次旅途)*2(最大单趟时间)/100(辆车)=3(分钟)。然而3分钟内100辆车是根本跑不完199趟的)。
虽然想到了这一点,但我采用的方法是尝试把int转换为double,这样改巨麻烦还容易错呜呜呜~
下午才想到办法,整数除法的误差只会使结果偏小,这样就只会对最大值造成重要影响,把可能出现的范围加上即可,这里有两种加法:
最大时间=旅途总数/车的数量*最大的单趟时间这样可能会还有一部分旅途数小于车数(即剩下的旅途数不需要所有的车都出动再跑一趟),这部分被舍去了,但其实还得出动部分车,即改为最大时间=(旅途总数/车的数量+1)*最大的单趟时间。最大时间=旅途总数*最大的单趟时间/车的数量这样可能会还有一部分时间的数值小于车数量的值(即剩下的时间还不够所有车一起再多走1分钟),被舍去了。但实际上剩下的时间还是需要完成的,即还是需要派出部分车,而派出的车一旦出动,就必须完整走完一趟。因此原式子改为最大时间=旅途总数*最大的单趟时间/车的数量+最大的单趟时间。
如果要用带整数除法的解法就一定要理解误差发生的含义,然后再想怎么弥补,切忌从数学的角度出发,认为整数除法后被舍去的是小数,而小数一定小于1,于是直接在任意除法结束后直接+1。例如上面的情况2,如果改成最大时间=旅途总数*最大的单趟时间/车的数量+1,好像是把舍去的小数部分都超额补回来了,但是从含义上理解,有可能所有的车都出动,但又都只走一分钟吗?今天在这个小地方卡了很久,说白了是自己懒得思考的锅。
如果想不通,就尽量避开整数除法,看了别人的题解,采取的是:最小时间=最小的单趟时间数;假设只有一辆车,则最大时间=旅途总数*最小的单趟时间。这样确实可以确定范围,并且避开整型除法(但只用乘法会超出int范围,必须转换为long long)。
既然只是确定范围,就不用在一种想法上死磕,卡住了就想想有没有更好理解、更不会产生误差的方法。
2.查找过程中max和min的值变化
常规二分查找是当取mid时,如果结果>目标时使max=mid-1;结果<目标时,min=mid+1,结果==目标时返回mid。
但是这道题有点不同:
- 取mid时,如果结果>目标,可能是因为这一分钟内到达目的地的车数太多,导致结果从上一分钟的小于目标变成这一分钟的超出目标。也就是说,虽然此时结果大于目标,但是mid可能就是要找的答案,因此,本题中计算结果>目标时,令max=mid。
- 如果结果==目标,可能是因为这一分钟内没有车到达目的地,也许之前的某一分钟才是最终答案。因此,本题中不能在计算结果==目标时return mid,要继续查找,知道最终范围内只剩一个值。
3.结束查找的出口
循环出口纠结了好一会儿是min<max还是min<=max,下次遇到记得这样考虑:
- 可以用2~4个来模拟一下,看最后会不会出现min>max情况,本题就不会出现这种情况,如果while(min<=max),将进入无限循环。
- 考虑如果用min<max,会不会有元素被忽略,例如如果返回的是bool型,且只在循环内查找,就必须用min<=max,或者单独判断只有一个元素的情况,本题不太可能只有一个元素,但就算只有一个元素,返回的也是这个元素(而非bool类型),所以可以不需要=。
其它一些细节
1.可以用max_element(s.begin(),s.end) ,min_element(s.begin(),s.end) 返回最大最小值对应的迭代器(带*调用哦),这样比起sort(),再取头尾,有明显的效率提升。
auto most =max_element(time.begin(), time.end());
auto least =min_element(time.begin(), time.end());
代码
class Solution {
public:
long long minimumTime(vector<int>& time, int totalTrips) {
long long total;
auto most =max_element(time.begin(), time.end());
auto least =min_element(time.begin(), time.end());
long long max=(long long)((long long)totalTrips* (*most)/time.size()+time[time.size()-1]);
long long min=(long long)(totalTrips/time.size())*(*least);
long long mid=(min+max)/2;
while(min<max){
total=0;
for(int j:time){
total+=mid/j;
}
if(total>=totalTrips){
max=mid;
}
else if(total<totalTrips){
min=mid+1;
}
mid=(min+max)/2;
}return mid;
}
};