【二分查找】一篇文章带你彻底理解二分查找

半年前之前写过一道二分查找的题,
学的时候老师说是有序才可用,做题的时候题解说有二段性就可以用,当时是真的苦恼,难受好几天,今天终于搞懂了,
看到以前写的二分查找博客深感实在是low,什么都没讲清楚,
遂今天重写一篇,希望可以帮助到当时和我一样的同学。

普通版二分查找:

普通版的二分查找是比较简单的,我们在学习C语言时应该都接触过。
但是我们还是要引一下。
二分查找题目在这里插入图片描述

暴力循环:

暴力就很简单,直接遍历一遍,这样的时间复杂度是O(N)

二分查找:

暴力的效率低的原因是没有利用单调性,因此我们要加以利用,

当我们选择中间的数时,
中间的数与target进行比较就可以得到两段区间,
得到一段小于target的值,另一段大于target,
这也就是我们常说的二段性,根据规则可以划分为两段。

这时可能会有同学有疑问,我选择3/1的位置可以嘛或者…
答案都是可以的,但是我们还是要选择中间的数(效率最高),因为这是通过数学的方法严格证明出来的,数学期望什么的

明白二段性后我们就可以根据你的需要进而选择哪左段还是右段。
然后一直循环,直到最后得到结果或得到这个数是否存在

话不多少直接进入代码部分。

代码:

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

关于这段代码还是有两个比较细节的点。

  1. 关于循环判断

我们要使用left <= right,为什么不用left < right呢在这里插入图片描述

  1. mid时防止溢出,关于mid的取法有两种:
    (right - left) + left或者(right - left + 1) + left
    在普通二分中这两种取mid都是可以的

引子:

在排序数组中查找元素的第一个和最后一个位置
由此题我们引出进阶版本
在这里插入图片描述
普通版本的二分解决这种重复的有序数组时间复杂度是比较高的。

我们由此引出可以解决这种情况的二分查找。

进阶版:

也就是万能二分!
进阶版其实与普通版相比也只是多了点细节而已。
我们先来看求左端时的情况。

左端:

先来看最重要的部分。

普通版的判断是

 if(nums[mid] < target)
     left = mid + 1;
 else if(nums[mid] > target)
     right = mid - 1;
 else
     return mid;

而求左端的是:

if(nums[mid] < target)
	left = mid + 1;
else if(nums[mid] >= target)
	right = mid

为什么呢?
在这里插入图片描述
好了,最重要的地方就结束了。
什么?这就结束了?

最重要的结束了,但是最细节的地方我们还没开始。
有两个细节部分

  1. 关于while内的判断部分

我们选择使用left < right
原因有二

  1. 我们相等时不需要进while循环内
    这里我们举3个例子
    在这里插入图片描述
  1. 在某种情况下会死循环
    在这里插入图片描述
  1. 关于取中的的取法
    在取左端时,我们选择(right - left) + left,原因在于如果我们选择(right - left + 1) + left,则会造成死循环。
    在这里插入图片描述

模版总结:

while(left < right)
{
	int mid = (right - left) + left;
	if(nums[mid] < target)
		left = mid + 1;
	else if(nums[mid] >= target)
		right = mid;
}

右端:

右端与左端只有一个细节区别(取中),推理思路也一样,故不详细解释,直接给出模版

while(left < right)
{
	int mid = (right - left + 1) + left;
	if(nums[mid] <= target)
		left = mid;
	else if(nums[mid] > target)
		right = mid - 1;
}

引子的代码实现:

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        vector<int> ans;
        if(nums.size() == 0)    
        {
            ans.push_back(-1);
            ans.push_back(-1);
            return ans;
        }

        int left = 0, right = nums.size()-1;
        //求左端
        while(left < right)
        {
            int mid = left + (right - left) / 2;
            if(nums[mid] < target)  left = mid + 1;
            else if(nums[mid] >= target) right = mid;
        }
        if(nums[left] == target)    ans.push_back(left);
        else 
        {
            ans.push_back(-1);
            ans.push_back(-1);
            return ans;
        }
        //求右端
        left = 0, right = nums.size()-1;
        while(left < right)
        {
            int mid = left + (right - left + 1) / 2;
            if(nums[mid] <= target)  left = mid;
            else if(nums[mid] >= target) right = mid - 1;
        }
        if(nums[left] == target)    ans.push_back(left);
        return ans;
    }
};
  • 27
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值