二分算法分析

模板讲解

二分的本质是在一段具有不同性质的连续区间中搜索边界点的过程,如下图所示
![[二分_1.png]]二分区间示意图

图1 二分区间示意图

如果我们将红色区间的性质设定为函数check(),那么我们就有搜索左侧边界点的基础模板

int l = 0, r = nums.size() - 1;
while (l < r) {
	int mid = (l + r + 1) >> 1;
	if (check(mid)) l = mid;
	else r = mid - 1;
}

这个模板中有几个需要注意的点:

  1. 当我们检查mid的性质时,因为我们是要找左侧边界点,所以当mid满足红色区间的性质时,左侧边界点可能存在的区间为[mid, r],因此我们令l = mid
  2. 当mid不满足红色区间的性质时,左侧边界点可能存在的区间为[l, mid - 1],因此我们令r = mid - 1
  3. 由于>>是向下取整,mid的取值是靠近l的,但是我们找到左侧边界点实际上是依靠r找到的(如果l先到左侧边界点,指针midl重合,此时出现死循环)因此如果我们要使mid的取值是靠近r就要int mid = (l + r + 1) >> 1

同理我们得到搜索右侧边界点的基础模板

int l = 0, r = nums.size() - 1;
while (l < r) {
	int mid = (l + r) >> 1;
	if (check(mid)) l = mid + 1;
	else r = mid;
}

这个模板中有几个需要注意的点:

  1. 当我们检查mid的性质时,因为我们是要找右侧边界点,所以当mid满足红色区间的性质时,右侧边界点可能存在的区间为[mid + 1, r],因此我们令l = mid + 1
  2. 当mid不满足红色区间的性质时,左侧边界点可能存在的区间为[l, mid],因此我们令r = mid
  3. 由于>>是向下取整,mid的取值是靠近l的,而且我们找到右侧边界点是依靠l找到的,所以此时使用要int mid = (l + r) >> 1没有问题

LeetCode例题

704. 二分查找

704. 二分查找 - 力扣(LeetCode)

可以通过寻找左侧边界点或者右侧边界点的方式解决这个问题,只是使用不同的check(),如果将target看作左侧边界点那么对应的check()就是nums[mid] <= target,如果将target看作右侧边界点那么对应的check()就是nums[mid] < target

左侧边界点对应第一个模板

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

右侧边界点对应第二个模板

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

35. 搜索插入位置

35. 搜索插入位置 - 力扣(LeetCode)

首先我们考虑一些特殊情况

  1. 如果target小于等于区间的左边界返回l
  2. 如果target大于区间的右边界返回r + 1
  3. 如果target等于区间的右边界返回r
    还有一种情况是nums[l] < target < nums[r],我们通过寻找左侧边界点的方式思考这个问题,选择nums[mid] <= target作为check(),最终通过r指针找到target(如果是区间中不包含target的情况r指针指向距离target最近偏小的值)
    综上所述得到代码如下
class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        int l = 0, r = nums.size() - 1;
        if (target <= nums[l]) return l;
        if (target > nums[r]) return r + 1;
        if (target == nums[r]) return r;
        while (l < r) {
            int mid = (l + r + 1) >> 1;
            if (nums[mid] <= target) l = mid;
            else r = mid - 1;
        }
        if (nums[r] == target) return r;
        else return {r + 1};
    }
};

34. 在排序数组中查找元素的第一个和最后一个位置

34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode)

这是一个非常适合二分算法的题目,我们套用两个模板解决这个问题

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        if (nums.empty()) return {-1, -1};
        int l = 0, r = nums.size() - 1;
        int leftBorder = 0, rightBorder = 0;
        while (l < r) {
            int mid = (l + r) >> 1;
            if (nums[mid] < target) l = mid + 1;
            else r = mid;
        }
        if (nums[l] != target) return {-1, -1};
        else {
            leftBorder = l;
            int l = 0, r = nums.size() - 1;
            while (l < r) {
                int mid = (l + r + 1) >> 1;
                if (nums[mid] <= target) l = mid;
                else r = mid - 1;
            }
            rightBorder = r;
        }
        return {leftBorder, rightBorder};
    }
};
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

行列因式重组hhh

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

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

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

打赏作者

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

抵扣说明:

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

余额充值