「数组」二分答案 / LeetCode 1870(C++)

概述

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;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值