二 分 算 法

二分查找:数组有序,寻找有序数组第一个大于等于a的数的位置

参考灵神的视频:

二分查找 红蓝染色法

左闭右闭:

int get_pos(vector<int>& nums, int target) {
        int l = 0, r = (int)nums.size() - 1;
        while (l <= r) {
            int mid = l + ((r - l) >> 1);
            if (nums[mid] < target)
                l = mid + 1;
            else
                r = mid - 1;
        }
        return l;
    }

 l-1一定指向小于8的数,r+1指向大于等于8的数;最后,l在第一个数的位置,r在第一个数的前一个位置

 左闭右开:

 int lower_bound2(vector<int> &nums, int target) {
        int left = 0, right = nums.size(); // 左闭右开区间 [left, right)
        while (left < right) { // 区间不为空
            // 循环不变量:
            // nums[left-1] < target
            // nums[right] >= target
            int mid = left + (right - left) / 2;
            if (nums[mid] < target) {
                left = mid + 1; // 范围缩小到 [mid+1, right)
            } else {
                right = mid; // 范围缩小到 [left, mid)
            }
        }
        return left; // 返回 left 还是 right 都行,因为循环结束后 left == right
    }

 左开右开:

int lower_bound3(vector<int> &nums, int target) {
        int left = -1, right = nums.size(); // 开区间 (left, right)
        while (left + 1 < right) { // 区间不为空
            // 循环不变量:
            // nums[left] < target
            // nums[right] >= target
            int mid = left + (right - left) / 2;
            if (nums[mid] < target) {
                left = mid; // 范围缩小到 (mid, right)
            } else {
                right = mid; // 范围缩小到 (left, mid)
            }
            // 也可以这样写
            // (nums[mid] < target ? left : right) = mid;
        }
        return right;//最后right指向答案,left指向前一个数
    }

 以上是求大于等于的情况,可以把二分查找分为四种类型(前提是数组的数都为整数):

1.大于等于x

2.大于x:转化为>=x+1

3.小于:>=x左边的数减1

4.小于等于:>x的数减1

二分答案法:

有一类题目,让你求满足条件的最值,如果直接去求,不好直接求出来,如果满足以下几个条件,

则可以使用二分答案法解决

1.最终的答案有一个粗略的范围

2.问题的答案和给定的条件之间存在单调性的关系

3.可以建立一个函数,判断某个值是否为答案

4.在答案所在的范围不断的二分搜索,直到找到答案



爱吃香蕉的kk

class Solution {
public:
    long f(vector<int>& nums, int m) {
        long sum = 0;
        for (auto i : nums) {
            sum += (i + m - 1) / m;
        }
        return sum;
    }
    int minEatingSpeed(vector<int>& piles, int h) {
        int l = 1;
        int r = 0;
        for (auto i : piles)
            r = max(r, i);
        while (l <= r) {
            int m = l + ((r - l) >> 1);
            if (f(piles, m) <= h) {
                r = m - 1;
            } else {
                l = m + 1;
            }
        }
        return l;
    }
};

  如果采用暴力方法,从1开始枚举速度,再去遍历数组计算时间。题目说如果那堆香蕉的数目不足

k,剩下的时间就不吃了,所以速度的最大值就是数组中的最大值,题目中数组元素的最大值到

10^9,明显超过了10^8,所以时间上会超时。

  就如刚才上述的分析,速度的最大值为数组元素的最大值,最小就到0,当速度变小时,花费的时

间会越多,在规定时间内吃完的可能性就会越小,这就使答案和给定的条件之间产生了单调性关

系。然后再看能不能写出一个函数来判断某一个数值是否为答案?其实是可以的,我们只要遍历一

遍数组,用数组的元素除以速度取上限值,累加和h判断大小。那么此题就可以用二分搜索的方式

解决

注:在我们的二分搜索介绍中,我们是对一个升序数组求第一个大于等于x的数值的位置,用上述二分搜索的模板可以解决"模板类二分搜索"。然而此题不太一样,对于速度从l到r,随着速度的增大,花费的时间就会越少,速度的区间和对应时间的区间的单调性是相反的,所以要求最小的速度,其实就是求第一个小于等于h的位置,此时应把二分搜索模板nums[mid]>=target;r=mid-1改为nums[mid]<=target;r=mid-1

分割数组的最大值

class Solution {
public:
    int get_parttion(vector<int>& nums, int mid) {
        int ans = 0;
        int sum = 0;
        for (auto i : nums) {
            if (i > mid)
                return -1;
            if (sum + i <= mid)
                sum += i;
            else {
                ans++;
                sum = i;
            }
        }
        return ans + 1;
    }
    int splitArray(vector<int>& nums, int k) {
        long l = 0, r = 0;
        for (auto i : nums)
            r += i;
        while (l <= r) {
            long mid = l + ((r - l) >> 1);
            int rem = get_parttion(nums, mid);
            if (rem <= k && rem != -1)
                r = mid - 1;
            else
                l = mid + 1;
        }
        return l;
    }
};

 求分割的部分的最小值

1.分割部分和最小可能为0,最大为所有数组的元素的和

2.分割的部分和越小,分割的部分就越多,二者之间存在单调性关系

3.想要判断一个数作为划分部分的和,可以将数组划分为多少部分,只要遍历数组来就可以求出划分的部分数

4.那么就可以用二分答案法不断的试探答案

时间复杂度:在区间l~r上不断的二分,其规模是log(sum);每次求划分的部分,就要遍历一遍数组,其规模是n,所以其时间复杂度为O(long(sum)*n),吃香蕉的哪个题的时间复杂度的计算与此题类似

二分答案题单:后续会更新解析

机器人跳跃问题

找出第k小的数对的距离

class Solution {
public:
    int f(vector<int>& nums, int mid) {
        int ans = 0;
        for (int l = 0, r = 0; l < nums.size(); l++) {
            while (r < nums.size() - 1 && nums[r + 1] - nums[l] <= mid)
                r++;
            ans += r - l;
        }
        return ans;
    }
    int smallestDistancePair(vector<int>& nums, int k) {
        sort(nums.begin(), nums.end());
        int l = 0, r = nums[nums.size() - 1] - nums[0];
        while (l <= r) {
            int mid = l + ((r - l) >> 1);
            int rem = f(nums, mid);
            if (rem >= k)
                r = mid - 1;
            else
                l = mid + 1;
        }
        return l;
    }
};

如果直接暴力求解,时间复杂度为O(N^2),根据数据大小可猜测超时了;关于数对的距离,是有

范围的,且数对的距离越大,其是第k小的数对的k的数值就会越大,二者之间存在单调性关系;然

后就是建立f函数,因为数组我们之前已经排好序了,只要建立一个滑动窗口,不停的收集左右边

界来统计答案,就可以求出此距离下是第几小的数对距离 

同时运行N台电脑的最长时间

class Solution {
public:
    bool f(vector<int>& batteries, long long mid, int n) {
        long long sum = 0;
        for (int i : batteries) {
            if (i >= mid)
                n--;
            else {
                sum += i;
            }
            if (sum >= n * mid)
                return true;
        }
        return false;
    }
    long long maxRunTime(int n, vector<int>& batteries) {
        long long l = 0, r = 0;
        for (int i : batteries)
            r += i;
        while (l <= r) {
            long long mid = l + ((r - l) >> 1);
            if (f(batteries, mid, n))
                l = mid + 1;
            else
                r = mid - 1;
        }
        return l - 1;
    }
};

    此题的关键是怎么分配电池的问题,当电池的供电量大于等于运行的时间时,到底是一直给一个

电脑供电划算,还是分配供电划算?其实一直给一个电脑供电是更划算的:如果这个电池给A,B供

电,当给A供电时,B还是需要其他的电池给他供电,然后再给B供电,不如一直给一个电池供电

更划算;其次是不足运行时间的电池的分配,其实只要满足所有电池的供电量不小于所有电脑的总

运行时间就可以,根据上面分析,就可以写f函数

   然后就是关于二分的过程,供电的时间粗略的估计在0~sum(所有的电池供电量之和)之间,只要

mid的供电时间满足,l=mid+1。注意,此题求的是最大的供电时间,最后r或l-1位置上的才是答

案,与上面的题目"求最小"注意区别

等待的服务时间

击杀怪兽的回合数

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值