二分答案法解决问题

做法:

1.估计 最终答案可能的范围 是什么,可以定的很粗略,因为等下二分很快就排除了。

2.分析 问题的答案给定的条件 之间的 单调性

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

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

核心点:找到答案和条件之间的单调性

410. 分割数组的最大值 - 力扣(LeetCode)

思路:可以假设为有sum个墙面需要粉刷匠去刷,现在最多有k个粉刷匠,如果每个粉刷匠每天只能粉刷mid个墙面,求一天刷完墙的mid的最小值。随着二分法的进行,每次mid下最少需要的粉刷匠的值就可以使用函数f得到,如果数量比k大,则粉刷匠刷的墙比mid小的,就会完不成任务,则需要刷的墙面数量要比mid个多。同理小于的时候,就可以每个人再少刷几面墙,直到刚刚好k个粉刷匠完成整个任务。

//使用f函数,计算出在mid值下分组,需要最少的分组数
   int f(vector<int>& nums, long limit)
    {
        int sum = 0;
        int parts = 1;
        for (int num : nums)
        {
            if (num > limit)
            {
                return INT_MAX;
            }
            if (sum + num > limit)//如果现在的和加上下一个大于limit,则已经满了.
            {
                parts++;
                sum = num;
            }
            else
            {
                sum += num;
            }
        }
        return parts;
    }
    int splitArray(vector<int>& nums, int k) {
        long sum = 0;
        for (int num : nums)
        {
            sum += num;
        }
        long left = 0;
        long right = sum;
        long mid = 0;
        long needk = 0;//实际需要的分组的个数
        int ans = 0;//实际的分组数
        while (left <=right)
        {
            mid = left + ((right - left) >> 1);//防止越界求中间值
            needk = f(nums, mid);
            //大于时,则最少需要的分组数已经大小k了,证明mid左边都不可以满足k的分组数
            if (needk > k)
            {
                left = mid + 1;
            }
            else//小于等于的时候,证明当前分组数比k更小,就可以再分的小一点,则mid的左边有更优解
            {
                right = mid - 1;
                ans = mid;
            }
        }
        return (int)ans;
    }
};
​
​

875. 爱吃香蕉的珂珂 - 力扣(LeetCode)

思路:显然知道猴子吃香蕉的时间来找每小时吃的数量很难做到,转换思路为通过锁定每小时吃的数量来对应时间,正好随着数量的变化时间也随之单调变化。猴子每小时吃的最少为1个,最多就是数组元素里最大的香蕉堆数,因为再大它吃完只会等待1小时结束。通过每小时吃的数量就可以确定需要吃完这些香蕉的天数,再比对h找到最优解即可。

​
class Solution {
public:
    long f(vector<int> piles, long limitspeed)
    {
        long time = 0;
        for (int pile : piles)
        {
            time += (pile + limitspeed - 1) / limitspeed;//向上取整
        }
        return time;
    }
​
    int minEatingSpeed(vector<int>& piles, int h) {
        //long right=maxpile(piles);//计算数组里最多的香蕉数
        int right = 0;
        for (int pile : piles)
        {
            right = max(pile, right);//向上取整
        }
        long left = 1;
        long mid = 0;
        int ans = -1;
        long needk = 0;
        while (left <= right)
        {
            mid = left + ((right - left) >> 1);
            needk = f(piles, mid);//计算在mid的速度下,吃完需要多久
            if (needk > (long)h)//该速度下,需要的时间大于h,说明速度太小了,所以mid前面的值都不可取,给右侧二分
            {
                left = mid + 1;
            }
            else if (needk <= (long)h)//该速度下,需要的时间小于h,说明mid的右边都可以满足,所以给mid左侧二分找更小速度
            {
                ans = mid;
                right = mid - 1;
            }
        }
        return ans;
    }
};

719. 找出第 K 小的数对距离 - 力扣(LeetCode)

思路:找第k小的数组对距离,因为当数组很大的时候,对数组差进行排序显然效率很低,转换思路,找数组差为x的时候,有多少对数组满足小于k对的,当x的值变化时,数组对数也单调变化。数组对差值的有范围就是最大值-最小值,随着差值的变化,找到刚好满足k个数组的解,就是最优解。

class Solution {
public:
    int f(vector<int>nums, int limit)
    {
        int realk = 0;
        for (int i = 0, r = 0; i < nums.size(); i++)
        {
            while (r + 1 < nums.size() && nums[r + 1] - nums[i] <= limit)
            {
                r++;
            }
            realk += r - i;
        }
        return realk;
    }
​
int smallestDistancePair(vector<int>& nums, int k) {
    sort(nums.begin(), nums.end());//sort函数,对数组进行排序
    int left = 0;
    int right = nums[nums.size() - 1];
    int mid = 0;
    int ans = -1;
    int realk = 0;
    while (left <= right)
    {
        mid = left + (right - left) / 2;
        realk = f(nums, mid);//计算mid为差值时,有多少个数组满足差小于mid
        if (realk < k)//两数差小于mid的对数小于k对,说明两数差值太小了,所以mid左边都不可能满足大于k
        {
            left = mid + 1;
        }
        else if (realk >= k)//两数差大于mid的对数比k多,说明mid的差太大了很多都满足,所以mid的右边肯定都满足
        {
            right = mid - 1;
            ans = mid;
        }
    }
    return ans;
}
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值