概述
LeetCode 1870:
给你一个浮点数
hour
,表示你到达办公室可用的总通勤时间。要到达办公室,你必须按给定次序乘坐n
趟列车。另给你一个长度为n
的整数数组dist
,其中dist[i]
表示第i
趟列车的行驶距离(单位是千米)。每趟列车均只能在整点发车,所以你可能需要在两趟列车之间等待一段时间。
- 例如,第
1
趟列车需要1.5
小时,那你必须再等待0.5
小时,搭乘在第 2 小时发车的第2
趟列车。返回能满足你准时到达办公室所要求全部列车的 最小正整数 时速(单位:千米每小时),如果无法准时到达,则返回
-1
。生成的测试用例保证答案不超过
1e7
,且hour
的 小数点后最多存在两位数字 。示例 1:
输入:dist = [1,3,2], hour = 6 输出:1 解释:速度为 1 时: - 第 1 趟列车运行需要 1/1 = 1 小时。 - 由于是在整数时间到达,可以立即换乘在第 1 小时发车的列车。第 2 趟列车运行需要 3/1 = 3 小时。 - 由于是在整数时间到达,可以立即换乘在第 4 小时发车的列车。第 3 趟列车运行需要 2/1 = 2 小时。 - 你将会恰好在第 6 小时到达。示例 2:
输入:dist = [1,3,2], hour = 2.7 输出:3 解释:速度为 3 时: - 第 1 趟列车运行需要 1/3 = 0.33333 小时。 - 由于不是在整数时间到达,故需要等待至第 1 小时才能搭乘列车。第 2 趟列车运行需要 3/3 = 1 小时。 - 由于是在整数时间到达,可以立即换乘在第 2 小时发车的列车。第 3 趟列车运行需要 2/3 = 0.66667 小时。 - 你将会在第 2.66667 小时到达。
我们在「数组」二分查找模版|二段性分析|恢复二段性 / LeetCode 35|33|81(C++)中介绍了对数组的下标进行二分的模版和二段性分析问题。
这一节我们来介绍二分查找的另一个应用:二分答案。
思路
区别于二分查找对数组下标进行二分,二分答案是对一个与数组无关的实数域进行二分,并将二分得到的值作用于数组判断可行性的过程。
我们还记得二分查找模版是这样的:
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int l=-1,r=nums.size();
while(l+1!=r){
int m=(l+r)/2;
if(nums[m]<target)l=m;
else r=m;
}
return r;
}
};
我们鲜明的发现,二分得到的m值是用于鉴别数组下标的,也即nums[m]。
而对于LeetCode 1870,则有另一种二分策略,也是二分答案模版:
bool check(int m,const vector<int>& dist,double& hour){
...
}
int minSpeedOnTime(vector<int>& dist, double hour) {
const int n=dist.size();
int l=0,r=1e7+1;
while(l+1!=r){
int m=(l+r)/2;
if(check(m,dist,hour))l=m;
else r=m;
}
return r!=int(1e7+1)?r:-1;
}
注意,我们是对一个实数域作二分,然后利用check判断它的可行性,题目保证答案不超过1e7,因此实数域右边界是1e7+1(+1是为了能够判断是否返回-1)。
二分答案通常是这样的: 题目希望你在有限制条件下取得不超过限制的最值。
在这种情况下的二分算法就是不停二分取值m,利用check(m)判断是否打破限制,如果打破限制就右移l/左移r来使m重新进入限制范围,即不停试探极限边界。
我们在接下来想一想check的内部实现。
算法过程
题目看起来很令人反感,我们中译中题意:
多人接力跑,全员相同速度,dist[i]是第i个人负责的路段长度,接力棒交接只能在整数时间进行,请找到在hours内跑完全程的最慢速度。
这样一来似乎就很明确了:
每一轮二分m传入check,我们遍历数组计算总时长,超时就加速m(即l=m),不然就试探更慢的m
(即r=m)。
值得注意的是:
除去最后一人,棒必须在每个人手上停留整数时间,因此计算时需向上取整。
而最后一个人不需要取整,以浮点数跑完即可。
bool check(int m,const vector<int>& dist,double& hour){
const int n=dist.size();
double time=0;//统计总时长
for(int i=0;i<n-1;i++){//除去最后一人,棒必须在每个人手上停留整数时间
if(dist[i]%m)time+=int(dist[i]/m+1);//向上取整
else time+=int(dist[i]/m);//整除
}
time+=1.0*dist.back()/m;//最后一人跑浮点数时间
return time>hour;//超时返回true,令l增大,即增大下一轮m,全员提速
}
复杂度
时间复杂度: O(nlogL)
n:数组长度
L:实数域长度(由题目给出1e7)
Code
class Solution {
public:
bool check(int m,const vector<int>& dist,double& hour){
const int n=dist.size();
double time=0;
for(int i=0;i<n-1;i++){
if(dist[i]%m)time+=int(dist[i]/m+1);
else time+=int(dist[i]/m);
}
time+=1.0*dist.back()/m;
return time>hour;
}
int minSpeedOnTime(vector<int>& dist, double hour) {
const int n=dist.size();
int l=0,r=1e7+1;
while(l+1!=r){
int m=l+r>>1;
if(check(m,dist,hour))l=m;
else r=m;
}
return r!=int(1e7+1)?r:-1;
}
};