二分法搜索

二分查找

大体写完了,懒得校对,因为最近都不想看到二分法这玩意了

二分法是什么

在数学中,其实我们是接触过二分法的概念的,
记得当时是在函数的单调性中,用来判断零点的近似值,

截图自百度百科

此时是在计算机中,处理的是一组组数据,本质上和函数是无差的,
比如,我们同样是要根据一段数据的中点进行判断,
而且,同样是要是一段”单调区间“,也就是要在有序数组中,
还有,数组中不能有重复元素,这一点对应“函数的一一对应”

以下一段话是维基百科的解释:

在计算机科学中,二分查找算法也称折半搜索算法,对数搜索算法,是一种在有序数组中查找某一特定元素的搜索算法。
搜索过程从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜索过程结束;
如果某一特定元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较。
如果在某一步骤数组为空,则代表找不到。这种搜索算法每一次比较都使搜索范围缩小一半。

为什么要使用二分法

如果是暴力求解,时间自由度就是O(n),用了二分法之后,时间自由度为O(logN),更快了

关于复杂度的简单认识可参考CSDN文章:ntq73
关于对数型的时间自由度的解释,这个博主的解释很有意思,可以看看:Ycccc

二分法的两种写法

极力推荐

  1. <【手把手带你撕出正确的二分法 | 二分查找法 | 二分搜索法 | LeetCode:704. 二分查找】 https://www.bilibili.com/video/BV1fA4y1o715?share_source=copy_web&vd_source=6809974e05da2ac6884b9251fab43087>
  2. 0704.二分查找.html#_704-二分查找
  3. qjNff

本部分内容基本根据卡哥的视频和他的文章得来。

全闭:[left, right]

程序里要坚守统一的区间格式

先要明确一点,
我们既然确定了使用某一种区间,就要一条道走到黑,
也就是说在这个程序中都要使用它,
这个容易理解,避免出错嘛,假如有人很NB能确保自己怎样都不会把自己绕晕,也就随便了

while循环中left<=right

随便举一个例子:

对于图中那个20的计算方法,通常直接(10+30)/ 2即可,
但是这里为了避免越界,还是用的最规范的方法

所以,按照这个统一的全闭写法,这里的while括号里的条件应该是left <= right
即当左边界的值大于右边界的时候,while循环就破开了。

关于这里的等号,他也是有实际意义的,
可以看到,我上面数轴上,10和30都是实心点,
他们在数轴上构成区间的意义是,从10到30,包括10和30的所有点的集合

随着二分的继续,或者就直接说取一个极端情况:这个包括端点的实心点的集合,里面此时只有一个点!
假设这个点为1, 如下图:

可以看到,在数轴上,这个1似乎就只是一个点,
但是,从我们数组或者刚刚“点的集合”来看,1其实也可以写成[1, 1]的

if判断中的right = middle - 1

然后把视角看向我刚刚分出来的,两个还没填写完成的全闭区间,
之所以没有填写完整,是因为这里有坑:

我们知道,我们二分法其实是分了三种情况的,以数组中middle代表元素的值与target进行判断,这三种情况分别用if-else if-else if来写
如果直接middle就是查找的那个,恭喜直接通关,
若是在左边的区间(假设数组元素递增),那么已经满足nums[middle] > target

这里的“右区间‘ 只是为了方便理解,不要当真
还有,既然分了是三种情况,就必须要明确那个中点是单独判断的,就不要把中点的实点带到左右区间了

所以说,此时的左区间,也是可以得出来的,
但是需要注意要满足坚守区间格式:

此时,右边区间已经没用了,直接将那个right拿过来表示右边界,
于是将 right 赋值为 middle - 1

左闭右开:[letf, right)

两个区间的区别

听卡哥说不只有这两种,但另外一种咱左开右闭都不喜欢用而已,
但最好不要用,毕竟两种都已经有点绕了,,,

对于全闭和左闭右开,当他们表示同一个int数组区间的时候:

可以看到,若是表示同一个区间,
如果说全闭的右边界是right,那么左闭右开区间的边界就是right+1,
准确来说是给定数组的尾下标加一

此时其实还有一个不太确定的,就是middle和right的判定
可以参考卡哥的例子,这个R=7是右边界,M =(1+7)/ 2 = 3
第二个二分里面,4是边界,M = (0+3)/ 2 = 1

无论哪一种区间,middle的求法都一样,不一样也行,但前提是你能保证你最后不会漏掉值

while循环中left < right

直接看那个极端情况吧:[1, 1),
左边的括号意思是可以等于1,但是右边括号说不等于1,两个怎么不打一架,
所以left不能等于right,
也就是说,while后面的条件是:left < right

if 判断中right = middle+1

题目_1

给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。

示例 1:

输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4

示例 2:

输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1

提示:

  • 你可以假设 nums 中的所有元素是不重复的。
  • n 将在 [1, 10000]之间。
  • nums 的每个元素都将在 [-9999, 9999]之间。

来源:力扣(LeetCode)
题目序号:704
链接:https://leetcode.cn/problems/binary-search

自己的代码

int search(int* nums, int numsSize, int target)
{
    int Left = 0;
    int Right = numsSize-1;
    int Middle = Left + (Right-Left)/2;

    while(Left <= Right)
    {
        Middle = Left + (Right-Left)/2; // 更新Middle

        if(nums[Middle] > target) // 左
        {
            Right = Middle - 1;
        }
        else if(nums[Middle] == target) // 中
        {
            return Middle;
        }
        else if(nums[Middle] < target) // 右
        {
            Left = Middle+1;
        }
    }

    return -1;

}

自己写的这个代码还算行,就不找其他代码了

题目_2

给你一个非负整数 x ,计算并返回 x 的 算术平方根 。

由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。

注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5 。

示例 1:

输入:x = 4
输出:2

示例 2:

输入:x = 8
输出:2
解释:8 的算术平方根是 2.82842…, 由于返回类型是整数,小数部分将被舍去。

提示:

0 <= x <= 231 - 1

来源:力扣(LeetCode)
题号:69
链接:https://leetcode.cn/problems/sqrtx

参考思路

本题在做的时候没啥思路,知道要用二分法但不知道咋用,
研究了下评论区一个大佬的代码,大抵知道思路了:

就是把给定的x当成由0递增1的有序数组的右极限(这里在意义上数组中不包括x这个点,有的只是开始时候被赋初值的max),
然后查找mid*mid

每一次判断mid*mid与x的大小后,都通过判断结果来改变左右极限,
mid*mid在数组中不断逼近x,

直到最后这个数组中最后只剩下一个元素或者两个元素,
此时初始数组的右边的元素是min或min+1,
左边的元素一定是min,
x此时被min*min(min+1)*(min+1)夹着,或直接等于这两中的一个
但无论是两种情况下的哪一种,此时都存在min==max,

此时直接返回min即可

参考代码

基本上就是copy的上面那位大佬的,只是在细节上处理了一下

int mySqrt(int x)
{
    int Max = x; 
    int Min = 0;
    int Mid = Min + (Max - Min)/2;

    if(1 == x) // 对下面提到的除数为0的情况单独讨论
    {
        return 1;
    }

    while(Max - Min > 1) // 此时Max与Min要么相等,要么相差1,已经是极限了
    {
        Mid = Min + (Max - Min)/2; // 更新Mid

        if(x/Mid < Mid) // 左。这里写的更直观点应该是 Mid*Mid < x,但是还是要避免越界的。可写成除的形式还要注意除数不能为0
        {
            Max = Mid; // 中间值的平方大于x,更新右边界
        }
        else if(x/Mid == Mid) // 中
        {
            return Mid;
        }
        else if(x/Mid > Mid) // 右
        {
            Min = Mid;
        }
    }

    return Min; // 写的时候发现力扣编译器的一个bug:这个位置不给return就报错。Max与Min要么相等,要么相差1,两种情况Min都是所求的那个值
}

题目_3

给定一个 正整数 num ,编写一个函数,如果 num 是一个完全平方数,则返回 true ,否则返回 false 。

进阶:不要 使用任何内置的库函数,如 sqrt 。

示例 1:

输入:num = 16
输出:true

示例 2:

输入:num = 14
输出:false

提示:
1 <= num <= 2^31 - 1

来源:力扣(LeetCode)
题号:367
链接:https://leetcode.cn/problems/valid-perfect-square

参考代码

思路跟上一题一样的,不细说,注释都懒得写了

bool isPerfectSquare(int num)
{
    int Max = num;
    int Min = 0;
    int Mid = Min + (Max - Min)/2;

    if(1 == num)
    {
        return true;
    }

    while(Max - Min > 1)
    {
        Mid = Min + (Max - Min)/2;

        if(num/Mid < Mid)
        {
            Max = Mid;
        }
        else if(num/Mid == Mid) // 由于用的是int,所以存在5/2 == 2的情况,需要用if来再次筛选
        {
            if(num%Mid == 0)
            {
                return Mid;
            }
            Min = Mid;
        }
        else if(num/Mid > Mid)
        {
            Min = Mid;
        }
    }

    return 0;

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值