算法【二分答案法】

本文需掌握二分搜索。

二分答案法

1.估计最终答案可能的范围是什么,可以定的粗略,反正二分不了几次。

2.分析问题的答案和给定条件之间的单调性,大部分时候只需要用到自然智慧。

3.建立一个f函数,当答案固定的情况下,判断给定的条件是否达标。

4.在最终答案可能的范围上不断二分搜索,每次用f函数判断,直到二分结束,找到最合适的答案。

核心点:分析单调性、建立f函数

下面通过几个题目加深理解。

题目一

测试链接:https://leetcode.cn/problems/koko-eating-bananas/

分析:可以通过题目得到答案的大致范围,通过对答案范围的二分搜索,将每次搜索的答案进行判定,该答案是否合法。搜索完毕即可得到最优的答案。代码如下。

class Solution {
public:
    bool f(vector<int>& piles, int h, int k){
        long long hours = 0;
        int length = piles.size();
        for(int i = 0;i < length;++i){
            hours += ((piles[i] + k - 1) / k);
        }
        return hours <= h;
    }
    int minEatingSpeed(vector<int>& piles, int h) {
        long long ans;
        int left = 1;
        int right = *(max_element(piles.begin(), piles.end()));
        int middle;
        while (left <= right)
        {
            middle = left + ((right - left) >> 1);
            if(f(piles, h, middle)){
                right = middle - 1;
                ans = middle;
            }else{
                left = middle + 1;
            }
        }
        return ans;
    }
};

其中,left和right分别是答案范围的左边界和右边界。f方法返回以k速度吃香蕉是否能在h小时内吃完。

题目二

测试链接:https://leetcode.cn/problems/split-array-largest-sum/

分析:二分答案法思路都差不多,主要是f函数的意义,范围的设置等,后面的题目不再赘述。代码如下。

class Solution {
public:
    bool f(vector<int>& nums, int he, int k){
        int group = 0;
        int sum = 0;
        for(int i = 0;i < nums.size();++i){
            if(nums[i] > he){
                return false;
            }
            if(sum + nums[i] > he){
                sum = nums[i];
                ++group;
            }else{
                sum += nums[i];
            }
        }
        return (group + 1) <= k;
    }
    int splitArray(vector<int>& nums, int k) {
        int ans;
        int left = 0;
        int right = accumulate(nums.begin(), nums.end(), 0);
        int middle;
        while (left <= right)
        {
            middle = left + ((right - left) >> 1);
            if(f(nums, middle, k)){
                right = middle - 1;
                ans = middle;
            }else{
                left = middle + 1;
            }
        }
        return ans;
    }
};

其中,f方法返回的是将nums数组分为和不超过he的子数组的个数是否小于等于k。

题目三

测试链接:https://www.nowcoder.com/practice/7037a3d57bbd4336856b8e16a9cafd71

分析:代码如下。

#include <iostream>
#include <vector>
using namespace std;
int N;
int max_value;
vector <int> H;
bool f(int E){
    for(int i = 0;i < N;++i){
        E += (E - H[i]);
        if(E >= max_value){
            return true;
        }
        if(E < 0){
            return false;
        }
    }
    return true;
}
int main(void){
    int ans;
    int left = 0;
    int right = 0;
    int middle;
    scanf("%d", &N);
    H.assign(N, 0);
    for(int i = 0;i < N;++i){
        scanf("%d", &H[i]);
        right = right > H[i] ? right : H[i];
    }
    max_value = right;
    while (left <= right)
    {
        middle = left + ((right - left) >> 1);
        if(f(middle)){
            right = middle - 1;
            ans = middle;
        }else{
            left = middle + 1;
        }
    }
    printf("%d", ans);
}

其中,f函数返回以E能量值出发能否到终点。

题目四

测试链接:https://leetcode.cn/problems/find-k-th-smallest-pair-distance/

分析:代码如下。

class Solution {
public:
    bool f(vector<int>& nums, int k, int value, int length){
        int num = 0;
        for(int left = 0, right = 1;left < length && right < length;++right){
            if(nums[right] - nums[left] < value){
                num += (right - left);
            }else{
                while (nums[right] - nums[left] >= value && left < right)
                {
                    ++left;
                }
                num += (right - left);
            }
        }
        return num >= k;
    }
    int smallestDistancePair(vector<int>& nums, int k) {
        sort(nums.begin(), nums.end());
        int length = nums.size();
        int ans;
        int left = 0;
        int right = nums[length-1] - nums[0];
        int middle;
        while (left <= right)
        {
            middle = left + ((right - left) >> 1);
            if(f(nums, k, middle, length)){
                right = middle - 1;
            }else{
                left = middle + 1;
                ans = middle;
            }
        }
        return ans;
    }
};

其中,f方法返回在nums数组中比value小的数对的个数是否大于等于k,通过nums数组中比value小的数对的个数采用滑动窗口求解。

题目五

测试链接:https://leetcode.cn/problems/maximum-running-time-of-n-computers/

分析:代码如下。

class Solution {
public:
    bool f(vector<int>& batteries, int n, long long minutes, int index){
        int num = 0;
        while (index - num >= 0 && batteries[index-num] > minutes)
        {
            ++num;
        }
        n -= num;
        index -= num;
        long long sum = 0;
        for(int i = 0;i <= index;++i){
            sum += batteries[i];
        }
        return sum >= (long long)n * (long long)minutes;
    }
    long long maxRunTime(int n, vector<int>& batteries) {
        long long ans;
        long long left = 0;
        long long right = accumulate(batteries.begin(), batteries.end(), (long long)0);
        long long middle;
        int length = batteries.size();
        sort(batteries.begin(), batteries.end());
        while (left <= right)
        {
            middle = left + ((right - left) >> 1);
            if(f(batteries, n, middle, length-1)){
                ans = middle;
                left = middle + 1;
            }else{
                right = middle - 1;
            }
        }
        return ans;
    }
};

其中,f方法返回n台电脑能够同时运行minutes分钟。

采用这种思路,速度较慢,可以加入一个贪心优化一下。代码如下。

class Solution {
public:
    bool f(vector<int>& batteries, int n, long long minutes, int length){
        long long sum = 0;
        for(int i = 0;i < length;++i){
            if(batteries[i] > minutes){
                --n;
            }else{
                sum += batteries[i];
            }
            if(sum >= (long long)n * (long long)minutes){
                return true;
            }
        }
        return false;
    }
    long long maxRunTime(int n, vector<int>& batteries) {
        long long ans;
        long long left = 0;
        long long right;
        long long middle;
        long long sum = 0;
        int max_value = 0;
        int length = batteries.size();
        for(int i = 0;i < length;++i){
            sum += batteries[i];
            max_value = max_value > batteries[i] ? max_value : batteries[i];
        }
        right = sum;
        if(sum >= (long long)max_value * (long long)n){
            return sum / n;
        }
        while (left <= right)
        {
            middle = left + ((right - left) >> 1);
            if(f(batteries, n, middle, length)){
                ans = middle;
                left = middle + 1;
            }else{
                right = middle - 1;
            }
        }
        return ans;
    }
};

加入了如果数组的和大于等于数组最大值与电脑台数的乘积,则直接返回数组和除以电脑台数即是答案的判断。其他部分,与之前思路一样,改变一些写法。

题目六

题目描述:计算等位时间。给定一个数组arr长度为n,表示n个服务员,每服务一个客人的时间。给定一个正数m,表示有m个人等位,如果你是刚来的人,每个客人都遵循有空位就上的原则。请问你需要等多久?假设m远远大于n,比如n <= 10^3, m <= 10^9,该怎么做是最优解?

谷歌的面试,这个题连考了2个月。

分析:代码如下。

class Solution {
public:
    bool f(vector<int>& arr, int m, int minutes, int length){
        int sum = 0;
        for(int i = 0;i < length;++i){
            sum += ((minutes + arr[i] - 1)/ arr[i]);
        }
        return sum >= m+1;
    }
    int WaitTime(vector<int>& arr, int m) {
        int ans;
        int length = arr.size();
        int left = 0;
        int right = *(min_element(arr.begin(), arr.end()));
        int middle;
        while (left <= right)
        {
            middle = left + ((right - left) >> 1);
            if(f(arr, m, middle, length)){
                right = middle - 1;
                ans = middle;
            }else{
                left = middle + 1;
            }
        }
        return ans;
    }
};

其中,f方法返回minutes分钟能不能服务到自己。

题目七

题目描述:

刀砍毒杀怪兽问题。怪兽的初始血量是一个整数hp,给出每一回合刀砍和毒杀的数值cuts和poisons。第i回合如果用刀砍,怪兽在这回合会直接损失cuts[i]的血,不再有后续效果。第i回合如果用毒杀,怪兽在这回合不会损失血量,但是之后每回合都损失poisons[i]的血量。并且你选择的所有毒杀效果,在之后的回合会叠加。两个数组cuts、poisons,长度都是n,代表你一共可以进行n回合。每一回合你只能选择刀砍或者毒杀中的一个动作。如果你在n个回合内没有直接杀死怪兽,意味着你已经无法有新的行动了。但是怪兽如果有中毒效果的话,那么怪兽依然会不停扣血,直到血量耗尽的那回合死掉。返回至少多少回合怪兽会死掉。

数据范围 : 1<=n<=10^5;1<=hp<=10^9;1<=cuts[i]、poisons[i]<=10^9

真实大厂算法笔试题。

分析:代码如下。

class Solution {
public:
    bool f(int hp, vector<int>& cuts, vector<int>& poisons, int round, int length){
        for(int i = 0;i < length && i < round;++i){
            if(cuts[i] >= (round - i - 1) * poisons[i]){
                hp -= cuts[i];
            }else{
                hp -= ((round - i - 1) * poisons[i]);
            }
            if(hp <= 0){
                return true;
            }
        }
        return false;
    }
    int ShortestRound(int hp, vector<int>& cuts, vector<int>& poisons) {
        int ans;
        int length = cuts.size();
        int left = 1;
        int right = hp+1;
        int middle;
        while (left <= right)
        {
            middle = left + ((right - left) >> 1);
            if(f(hp, cuts, poisons, middle, length)){
                right = middle - 1;
                ans = middle;
            }else{
                left = middle + 1;
            }
        }
        return ans;
    }
};

其中,f方法返回在round回合能否杀死boss。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

还有糕手

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值