高效掌握二分查找:从基础到进阶

进阶:二分查找的函数实现 关于二分查找的基础请见我之前的博客。二分查找的实现 今天呢,我就和大家一起聊聊二分查找的函数实现。 随着我们水平的提升,我们越来越多的使用函数,使用函数不仅可以使我们的代码变得简洁,还可以使程序使程序流程结构化。 ... 阅读详情

目录

前言

题1 二分查找:

思考:

解法一:暴力解法

解法二:利用二分查找的思想

参考代码:

题2 在排序数组中查找元素的第一个和最后一个位置:

思考:

解法一:暴力解法

解法二:朴素二分

解法三:在朴素二分上进行优化

part1:查找左区间端点

part2:查找右区间端点

思路总结:

参考代码:

题3 搜索插入位置:

思考:

解法一:暴力解法

解法二:二分(利用二段性)

参考代码:

题4 x 的平方根 :

思考:

解法一:暴力解法

解法二:二分(利用二段性)

参考代码:

题5 山脉数组的峰顶索引 :

思考:

解法一:暴力解法

解法二:二分(利用二段性)

参考代码:

题6寻找峰值 :

思考:

解法一:暴力解法

解法二:二分,优化暴力解法

参考代码1:

参考代码2:

题7 寻找旋转排序数组中的最小值:

思考:

解法一:暴力解法

解法二:优化暴力解法

参考代码:

题8 点名 :

思考:

解法一:直接遍历

参考代码1:

解法二:哈希表

参考代码2:

解法三:位运算

参考代码3:

解法四:数学方法:高斯求和方式

参考代码4:

解法五:二分

参考代码5:

总结


前言

路漫漫其修远兮,吾将上下而求索;


这是题集,大家可以先尝试做一下:

  1. 704. 二分查找
  2. 34. 在排序数组中查找元素的第一个和最后一个位置
  3. 35. 搜索插入位置
  4. 69. x 的平方根
  5. 852. 山脉数组的峰顶索引
  6. 162. 寻找峰值
  7. 153. 寻找旋转排序数组中的最小值
  8. LCR 173. 点名

做题之前,我们先介绍一下什么是二分算法;

特点:细节处理的比较多,容易写出死循环;

不仅数组有序的情况下可以使用二分,数组无序的情况下也可以使用二分;核心在于:只要数组中有规律能够使用二分进行查找,便就可以使用二分;

二分查找相关的模板有3个:

  • 1、朴素的二分查找
  • 2、查找左边界的二分模板
  • 3、查找右边界的二分模板

我们先了解一下朴素的二分查找模板,剩下两个在做题中掌握然后再总结;

需要注意的是,我们在计算中间mid 的时候: int mid = left +(right-left)/2; 当然也可以写做:int mid = left + (right - left +1)/2; 这两种写法的区别在于[left,right] 区间中的数据个数为奇数、偶数的时候,如下图:

在朴素版本中,将中间点定义在何处都是一样的;以上两种计算中间点的方法均可适用,因为朴素的二分的核心在于:找到一个分界点,然后根据这个分界点来划分左、右区间,并不会关心中间点的位置是偏左还是偏右;


题1 二分查找:

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

题干:

思考:

解法一:暴力解法

从左往右遍历该数组一遍,找到target 便停止操作,此时返回下标即可;倘若遍历完了整个数组均没有找到,就返回-1;

解法二:利用二分查找的思想

本题给的数组中的数据就是有序的;

在一个数组中找mid (可以将mid 当作为一种标识),将mid 指向地数据与target 进行比较,而以mid 为分界线,该数组被划分为了两个区域,比nums[mid] 小的,比nums[mid] 大的;其中,我们可以根据target 与 nums[mid] 的大小关系而排除一个区域,而继续去另外一个没有排除的区域中查找……那么此时就可以认为数据具有二段行,从而可以使用二分法来解决;

二分查找的本质:当数据具有二分性的时候,与数组中的数据是否有序无关,如下:

关于mid,我们仅仅是将它当作一个分界线标志:

选择 1/2 ,是因为涉及数学中的概率学问题,即求数学期望,选择1/2t 的位置,算法的时间复杂度最好;

因为我们要频繁地找中点,并且频繁地确定接下来所要查找target 地区间,所以我们可以定义三指针:left,指向区间的左端点;right , 指向区间的右端点; mid , 表示区间的中点;

假设mid 指向的值为x ,那么x 与 target 就有三种大小关系,如下:

此处还存在许多细节问题:

Q1:循环结束的条件是什么?

  • 当left 和right 维护的区间之中只有一个数据的时候,该数据仍需与target 进行比较;所以循环结束的条件: left<=right;

Q2:该二分查找算法为什么是正确的呢?

  • 二分查找本质是从暴力解法上进行优化的;暴力解法既然正确,那么二分也一定正确;

Q3:该算法的时间复杂度?

  • 最差就是该数组中没有所要查找的数据或者就在最后一次找个目标数据,所要查找的次数为 \log_{2}N,所以时间复杂度为 O(logN);

参考代码:

    int search(vector<int>& nums, int target) 
    {
        int n = nums.size();
        int left = 0, right = n-1;
        while(left <= right)
        {
            int mid = left + (right-left)/2;
            if(nums[mid]<target) left = mid+1;
            else if(nums[mid] > target) right = mid-1;
            else return mid;
        }
        //走到这里就是没有找到
        return -1;
    }

题2 在排序数组中查找元素的第一个和最后一个位置:

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

题干:

思考:

题干中说的非递减顺序排序的意思就是该序列要么递增,要么相等;

解法一:暴力解法

从前往后遍历,遇到第一个等于target 的值时用变量begin 标记其下标,然后再用end 标记其结尾,当遍历完begin 和 end 均没有所要维护的区间的时候,说明没有找到,返回{-1,-1} 就可以了;

解法二:朴素二分

根据中间值(mid 所指向的值)进行分情况讨论;

 

但是只是利用mid 就只能找到与target 相等的值,并不能找到值与target 相等的区间;就需要去mid 的前、后继续查找;当整个数组中的值均为target的时候,此时的实践复杂度为O(N);

朴素的二分最然也可以解决问题,但是在此处解决问题就失去了二分查找速度快的特性;

解法三:在朴素二分上进行优化

还记得前面我们所说的二分查找的本质吗?二分查找的本质是二段性;

我们将题干要求划分为两个小目标:1、查找左区间端点  2、查找右区间端点

part1:查找左区间端点

当我们找左端点时,该左端点的数据全是小于target 的,而其右边均是大于等于target 的,显然地将数据分为了两段,如下图:

朴素二分怎么做的呢?

情况二,当x <=t 的时候,mid 指向的数据可能刚好等于t ,那么将right 更新为 mid -1 ,便会少记录一个数据的范围 , 故而就不能直接将right 更新为mid -1; 而是让right 更新到mid 的位置即可;

即:

在朴素二分之中进行了修正,当nums[mid] 小于target 的时候,更新的策略不变: left = mid +1;但是当nums[mid] >= target 的时候,因为mid 指向的数据有可能等于target ,所以更新策略为 right = mid;

用这样的策略来解决问题还需要注意有两个细节需要处理:1、循环条件 2、求中间点的操作

   Q: 这两个条件该选哪一个?

分情况讨论left 、right 区间:

ret 为最左端点的位置;

1、当left 与 right 区间中有结果;

当left 开始在不合法区间,而right 处于合法区间(left 一直在不合法的区间移动,right 一直在合法的区间移动);而right 在合法区间移动一定不会超过ret 这个点,即right 永远都是大于等于target 的,而left 是永远想要跳过左边不合法的区域;那么left 与 right 相遇的位置一定是ret ,即当left 与 right 相等的时候该下标就是左区间的最终结果,无需判断,直接返回即可;故而从这一情况来看,循环条件应该为 left < right;

2、left 与 right 之间全是大于等于目标值target 的;

此时right 只会向左移动直到移动到与left 相遇为止;而当left 等于right 的时候,只需要判断left 指向的为止是否等于target 就可以了;如果等于target ,则说明该位置就是最终结果;不相等则返回{-1,-1}即可;并不需要进入循环之中判断当left == right 时,left 指向的数据是否为 target ;所以,就此情况而言,其循环条件也是 left < right;

3、left 与 right 区间之中全是小于目标值target 的

此时right 永远不会动而left 会一直向右移动;当 left == right 时,也不用进入循环之中判断,在循环外判断是否与target 相等就可以了;(此处一定不等于target ,因为[left,right] 之间全是小于target 的数据);

经过以上分情况分析,循环条件应该选择: left < right;因为当left == right 的时候就是最终结果,在循环外面判断即可,倘若进入循环中判断便会造成死循环,如下:

2、求中间点的操作

此处有两种方式来求中间点:

这两种形式的区别,无非就是当数据个数为偶数个的时候,用方法一求得的mid 是 偏左 位置的中间点,而用方法二求得的mid 是 偏右 位置的中间点;在朴素二分中无论是用方法一还是方法二来求中间点均可,但是在此处不行;为什么呢?当最后一次操作,数组中刚好只剩下两个数据的时候,如下图:

经过上述的分析,求区间左端点更适合使用方法一来求中间点;

查找左端点的两个细节问题;

  • 一是注意循环条件必为: left < right ;倘若left == right ,就已经是最终结果了,不用再进入循环进行判断;如果进入循环判断了就会陷入死循环;
  • 二是求中间点的计算方式必须是: mid = left + (right - left)/2; 即当数据个数为偶数个的时候,中间点需要偏左;
part2:查找右区间端点

假设中间点mid 指向的值为 x 

将x 与 target 之间的大小关系进行分情况讨论;

  • 因为是要找右区间端点即:左边的区域均是小于等于target 的,右边的区域均是大于target 的;所以划分依据变成了 x <=t  与 x>t;
  • 当 x <= t 的时候 , 更新left ,因为 此时mid 指向的数据可能等于 target ,所以left 的更新并不是 mid+ 1, 而是 left = mid;

(总结如下图)

 当x >t 的时候,更新right ,right = mid-1;

同样地需要处理两个细节问题:1、循环条件 2、求中间点的方式

此处对于循环条件地分析和查找区间左端点循环条件地分析一模一样,此处就不再赘述;

关于求中间点:

究竟是使用方法一还是方法二呢?

根据分析,当求区间右端点的时候更适合用方法二来求中间点;

思路总结:

同理,对于查找右端点的方法依旧是利用区间的二段性,即左边的区域均是小于等于target 的,右边的区域均是大于target 的,因此就可以使用二分来解决;其次在利用二分的时候,是在朴素二分的基础上更新了策略:当 x <= t , left = mid; 当x >t , right= mid-1;

查找左、右端点总结:

其实这就是剩下的两个模板:查找区间左端点的模板、查找区间右端点的模板:

记忆小技巧:下面出现-1 的时候,求mid 便会有 +1;

参考代码:

    vector<int> searchRange(vector<int>& nums, int target) 
    {
        int n = nums.size();
        if(n==0) return {-1,-1};
        int left = 0, right = n-1,begin = 0 , end = 0;
        //寻找区间左端点
        //左端点-> 左边均是小于 t,右边是大于等于t
        //        left = mid +1;       right = mid;
        // 让mid 位于偏左的位置上 : mid = left + (right - left)/2;
        while(left < right)
        {
            int mid = left + (right - left )/2;
            if(nums[mid]<target) 
            {
                left = mid+1;
            }
            else //nums[mid] >= target
            {
                right = mid;
            }
        }
        //出了循环就是left == right,判断指向的数据是否等于target
         if(nums[left] == target) begin = left;
         else return{-1,-1};
         //寻找区间右端点
         //右端点 --> 左边均是小于等于t , 右边均是大于t
         //         left = mid;            right = mid -1;
         // 让mid 位于偏右的位置上, mid = left + (right - left+1)/2
        left = 0, right = n-1;
        while(left < right)
        {
            int mid = left + (right - left +1)/2;
            if(nums[mid]<=target) 
            {
                left = mid;
            }
            else //nums[mid] > target
            {
                right = mid -1;
            }
        }
        end = right;//写left 和 right 都是一样的
        return {begin , end};
    }

题3 搜索插入位置:

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

题干:

思考:

解法一:暴力解法

直接按照数据的升序进行查找,虽然时间复杂度不符合要求,但不失为一种方法;

解法二:二分(利用二段性)

此题分为两种情况,一是可以找到目标值,而是找不到目标值,但是需要返回这个目标值需要插入的位置,而所要插入的位置又有区分:越界、未越界(第一次出现比目标值大的数据);

结合起来就是寻找大于等于目标值target 数据的下标;

可见该数据具有二段性,一段是小于target 的,一段是大于等于target 的;

我们要找到的是大于等于target 的第一个数据的下标;如果整个数组均是小于target 的数据,那么就返回这个数组的个数;

那么我们此处可以使用查找区间左端点的模板来解决;

我们先来回忆下下这个模板,首先是这个区间的划分依据:其左边是小于target 的,右边是大于等于target 的,假设指针mid 指向的数据未x; 当 x < target 的时候, left = mid +1; 但 x >= target 的时候,right = mid; 因为是right = mid , 为了避免死循环,中间点的计算应该偏左,即计算中间点: mid = left + (right-left)/2; 同样地,还可以通过我们地小技巧来记忆,因为left 和 right 中没有-1 ,所以计算mid 的方式不用加一;求区间左端点的伪代码如下:

参考代码:

    int searchInsert(vector<int>& nums, int target) 
    {
        //使用二分查找区间左端点的代码模板
        int n = nums.size();
        int left = 0, right = n-1;
        while(left < right)
        {
            int mid = left + (right - left)/2;
            if(nums[mid] < target) left = mid+1;
            else right = mid;
        }
        if(nums[left]<target) return n;
        else return left;
    }

题4 x 的平方根 :

69. x 的平方根 - 力扣(LeetCode)

题干:

思考:

求一个数x的平方根,结果的平方一定小于等于x;

解法一:暴力解法

枚举1~x 的数据,然后依次计算平方根,从左往右暴力查找与x 最接近且小于等于x 的数;

例如,x  = 17;

在枚举的过程,数据本身就是有序的,且符合二段性;即我们可以将数据划分为小于等于target 以及大于target 两段;

解法二:二分(利用二段性)

即本题我们可以利用查找区间右端点的模板来解决;

我们先来回忆一下查找右端点的模板,我们会将数据划分为两段,其左边小于等于目标值target ,其右边会大于目标值target ,假设指针mid 指向的数据为x;当 x <= target  的时候, left = mid;当 x > target  的时候,right = mid -1; 只有两个结点的时候,要避免 left == mid , 所以计算的mid 需要偏右,即mid 的计算 : mid = left+(right-left+1)/2;查找区间右端点模板伪代码如下:

本题还有一个细节:因为数据范围:,所以为了防止溢出,mid 的类型应该为long long;其次是需要处理当x 为0 的边界情况;

参考代码:

    int mySqrt(int x)
     {
        //处理边界情况
        if(x == 0) return 0;
        int left = 1, right = x;
        while(left < right)
        {
            long long mid = left + (right-left+1)/2;
            if(mid*mid <= x) left = mid;
            else right = mid - 1;
        }
        //出了循环left == right ,随便返回一个就可以了
        return left;  
    }

题5 山脉数组的峰顶索引 :

852. 山脉数组的峰顶索引 - 力扣(LeetCode)

思考:

也就是说这个数组中的数据是先递增然后递减;

解法一:暴力解法

利用指针遍历数据判断当前的数据与下一个数据间的大小关系,遇到第一个当前数据大于下一个数据的时候,当前的下标就是我们所要查找的下标;

接下来我们需要思考的是,我们是否能在暴力解法的基础上进行优化?

显然,数组nums 被天然地分成了两段,即该数据具有二段性,如下:

解法二:二分(利用二段性)

可以将数组分为两段,一段是前面地数据均比当前数据小,一段是前面地数据均比当前数据大;我们的目的是为寻找前面的数据小于当前数据区间的右端点;

那么当 nums[mid-1] < nums[mid] 的时候,left = mid; 当nums[mid-1] > nums[mid] 的时候,right = mid-1; 如果只剩下两个数据,因为我们调整的式子中有 left = mid , 为了避免进入死循环,我们求得的mid 就要偏右,即计算mid : mid = left + (right-left+1)/2; 同样地,还可以利用小技巧,当出现-1 的时候,计算mid 中就要+1;

其中需要注意的是,要保障 mid -1 ,是合法的,也就是说mid 不可以为0;而数据个数最少是3个,也就是说需要将left 初始化为1,而不是0;

其实此处就是求区间右端点的模板,其伪代码如下:

参考代码:

    int peakIndexInMountainArray(vector<int>& arr) 
    {
        //数据具有二段性,用二分寻找区间右端点的模板就可以解决
        int n = arr.size();
        int left = 1,right = n-1;
        while(left < right)
        {
            int mid = left + (right - left +1)/2;
            if(arr[mid-1] < arr[mid]) left = mid;
            else// 大于
            right = mid-1;
        }
        //出了循环就意味着 left == right
        return left;
    }

题6寻找峰值 :

162. 寻找峰
值 - 力扣(LeetCode)

思考:

本题和与上一道题没有什么区别,只不过本题中的数据含有多个峰值,即数据中的趋势呈波浪状;

解法一:暴力解法

从第一个位置开始,一直往后查找,其中需要分情况讨论;

  • 情况一:递减,即直接向下走;那么下标为0的数据就是其中的峰值,可以返回;
  • 情况二:递增,一直在上坡,走到了数据末尾了都还没有下坡,返回最后一个位置就可以了;
  • 情况三:波浪,既有上坡又有下坡,只需要找到开始下坡的位置并返回即可;

解法二:二分,优化暴力解法

我们先抽象一下暴力解法:根据nums[i]  与 nums[i+1] 的特点,我们可以分情况讨论:

(需要注意的是,我们目标的区间要是同一区间,要么是找上升区间的端点要么是找下降区间的端点,此处先分析找下降区间的左端点)

  • 1、nums[i] > nums[i+1] 下降趋势,需要找到下降的这个区间的右端点,这个右端点的左边均是上升趋势,右边是下降趋势;而寻找一个区间的右端点:right = i;
  • 2、nums[i] < nums[i+1] 上升趋势,需要找到下一个下降区间的左端点,这个左端点的左边均是上升趋势,右边均是下降趋势;而寻找一个区间的左端点:left = i+1;

如下图:

因为式子中 right = i , 按道理说为了避免死循环,所求得到的中间点必须要是偏左的,即计算mid: mid = left + (right-left) /2;

参考代码1:

    int findPeakElement(vector<int>& nums) 
    {
        //寻找下降段的左端点
        int n = nums.size();
        int left = 0 , right = n-1;
        while(left < right)
        {
            int mid = left + (right - left)/2;
            if(nums[mid] < nums[mid+1]) left = mid+1;
            else right = mid;
        }
        //出了循环left == right
        return left;
    }

同样地,我们还可以找上升区间的右端点:

  • 1、nums[i] > nums[i+1] 下降趋势,right = mid-1;
  • 2、nums[i] < nums[i+1] 上升趋势, left = mid;

求 mid : mid = left + (right - left +1)/2;

参考代码2:

    int findPeakElement(vector<int>& nums) 
    {
        //寻找上升区间的右端点
        int n = nums.size();
        int left = 0 , right = n-1;
        while(left < right)
        {
            int mid = left + (right - left +1)/2;
            if(nums[mid-1] < nums[mid]) left = mid;
            else  right = mid-1;
        }
        return left;
    }

题7 寻找旋转排序数组中的最小值:

153. 寻找旋转排序数组中的最小值 - 力扣(LeetCode)

思考:

数组原本中的数据是升序的,但是会经过旋转,那么就会有一下两种情况:

解法一:暴力解法

直接遍历寻找最小值

解法二:优化暴力解法

将nums 中数据进行旋转处理之后,会有两种情况,一是维持原状,而是被分成了两截;第一段,是从最前面开始呈现上升趋势,后一段也会呈现上升趋势,由于该数组之中全为不相同的元素,多么明显的二段性,完全可以使用二分来解决;

经过观察可以发现,在AB段之中呈现上升趋势,那么A点一定是AB段中最小的值;同理,在CD段中,C点是最小的值;显然地,A点是一定大于C点地,我们只要找到C点就可以了;而C点左边的数据全是大于C中的数据,其右边也全是大于自己的数据,没有数据上的单调性,但是分段是存在的;

Q: 这个时候如何解决呢?

我们将D点中的数据当作参照物,那么大于D点的就是上面的一段,小于D点的就是下面的一段;

假设nums 中的数据个数为n;当nums[i] <= nums[n-1] 就说明 i 下标所对应的数据位于下段;当nums[i] > nums[n-1] 就说明 i 下标所对应的数据位于上段;而我们的目标是求得下段区间的左端点;如下:

Q:我们能否以A点作为判断点?

  • 可以的,但是需要将第二种情况(全递增)进行特殊处理,如下图分析:

参考代码:

    int findMin(vector<int>& nums) 
    {
        //我们以最后一个数据作为划分段的依据,所以是查找区间的左端点
        int n = nums.size();
        int left = 0 ,right = n-1;
        while(left < right)
        {
            int mid = left + (right - left)/2;
            if(nums[mid] > nums[n-1]) left = mid+1;
            else right = mid;
        }
        return nums[left];
    }

题8 点名 :

LCR 173. 点名 - 力扣(LeetCode)

思考:

寻找顺序的缺失的数字,本题很简单;

解法一:直接遍历

直接遍历这个数组,因为此数组中的数据严格递增,我们只需要遍历借助于前后数据差是否为1便可;时间复杂度为O(N);

参考代码1:
    int takeAttendance(vector<int>& records) 
    {
        //直接遍历
        int n = records.size();
        for(int i = 0;i<n;i++)
        {
            if(records[i]!=i) return i;
        }
        return n;
    }

解法二:哈希表

借助于(n+1) 大小的哈希表去遍历数组,遍历结束后看哪一个哈希表中那个位置上是空的即可;时间复杂度为O(N);

参考代码2:
    int takeAttendance(vector<int>& records) 
    {
        //hash
        int n = records.size();
        int hash[10001] = {0};
        for(auto e: records) hash[e]++;
        for(int i = 0; i < n;i++) 
            if(hash[i]==0) return i;
        return n;
    }

解法三:位运算

利用^ (按位异或);按位异或的特点:相同为0相异为1,那么相同给的元素按位异或就会相互抵消;时间复杂度为O(N);

参考代码3:
    int takeAttendance(vector<int>& records) 
    {
        //利用按位异或
        int n = records.size();
        int ret = 0;
        for(int i = 0;i<=n;i++) ret^=i;
        for(auto e: records) ret^=e;
        return ret;
    }

解法四:数学方法:高斯求和方式

即等差数列的求和方式:((首项+末尾)*项数)/2;时间复杂度为O(N);

参考代码4:
    int takeAttendance(vector<int>& records) 
    {
        //利用按位异或
        int n = records.size();
        int ret = ((0+n)*(n+1))/2;
        for(auto e: records) ret-=e;
        return ret;
    }

解法五:二分

利用数组的二段性;因为数据中缺失了一个数,那么数据就会被分成两段,如下:

我们的目的就是找到B或者C,总得找个依据来划分数组吧?想要将数据划分为两段,似乎也是让B或者C作为判断点,但是我们的目的不就是找它们呢?显然,这个逻辑是不自洽的,那么这个思路也是不对的;

我们需要换个角度继续思考:

如果数据完全从0开始往后顺序,数据与下标是一一对应的,但倘若差一个数据那么数据与下标就会差1;这也是二段性,一段是数据与下标一一对应,一段是数据与下标差1;而我们的目标值为第二段区间的下标左端点,如下:

那么就可以利用二分中寻找一个区间左端点的模板来解决这个问题;当 nums[mid] == mid , left = mid+1; 当nums[mid] >mid 的时候, right = mid; 为了避免死循环,去所求的的mid 要偏左,故而mid 的计算为 : mid = left + (right-left)/2; 

参考代码5:
    int takeAttendance(vector<int>& records) 
    {
        //二分
        int n = records.size();
        int left = 0 , right = n;
        while(left < right)
        {
            int mid = left + (right - left)/2;
            if(records[mid] == mid) left = mid+1;
            else right = mid;
        }
        return left;
    }

总结

二分查找相关的模板有3个:

  • 1、朴素的二分查找

  • 2、查找左边界的二分模板

  • 3、查找右边界的二分模板

算法之Java编程基础与二分查找算法探秘 大家好!在编程的学习道路上,每一次深入探索都是成长的宝贵机会。今天,咱们一起深入学习Java编程基础以及经典的二分查找算法相关知识,希望能和大家共同进步,收获满满干货! 阅读详情

相关推荐

Java实现数组的高效二分查找算法

二分查找算法作为计算机科学中的一个经典搜索算法,凭借其高效的性能和易于实现的特点,成为处理有序数据集的重要工具。通过对二分查找原理的深入理解和应用,可以显著提升数据搜索的效率,特别是在大规模数据处理场景中表现突出。大O表示法用于描述函数的运行时间或空间需求如何随输入规模的增加而变化。例如,如果一个算法的时间复杂度是O(n),那么算法的执行时间将与输入数据的大小成线性关系增长。时间复杂度是评价算法性能的关键指标之一,理解不同算法的时间复杂度对于选择合适的算法至关重要。

weixin_31419153的博客 973

【LeetCode】二分查找精选10题

LeetCode 二分查找精选10题 详解

Qiuhan_909的博客 7375

树莓派4B+红外传感器:手把手教你DIY一个能走黑线的智能小车(Python代码全解析)

本文详细介绍了如何使用树莓派4B和红外传感器DIY一个智能循迹小车,包括硬件配置、GPIO连接、Python代码实现及优化算法。通过动态PWM调速、状态机设计和赛道记忆算法,提升小车的循迹性能和稳定性。适合初学者和电子爱好者动手实践。

weixin_29188043的博客 269

二分查找及例题

二分查找

lebowskii的博客 971

华为云CodeHub保姆级教程:从零开始,5分钟搞定本地代码上传(含常见报错解决)

本文提供华为云CodeHub的保姆级教程,从零开始5分钟完成本地代码上传,涵盖环境准备、仓库创建、远程连接及常见报错解决。特别适合新手开发者快速掌握Git命令与云端协作流程,确保代码安全高效上传。

weixin_30443075的博客 480

工业界实战:小红书推荐系统中的AB测试策略解析——分层、Holdout与反转实验

本文深入解析了小红书推荐系统在工业界实践中采用的AB测试核心策略。重点阐述了分层实验如何解决多实验并行与干扰问题,Holdout机制如何作为评估黄金标准提供稳定基准,以及反转实验如何作为全量上线后的终极安全网进行风险控制。这些策略共同构成了科学、严谨的线上评估体系,是保障推荐系统迭代效果与决策质量的关键。

transformer2023的博客 477

算法之常见二分查找法题目

二分查找法是比较常见的一种算法,下面我列举的题目都是常见的二分算法题目。剑指 Offer II 072. 求平方根 leedcode 35. 搜索插入位置leedcode 367. 有效的完全平方数leedcode 34. 在排序数组中查找元素的第一个和最后一个位置

ChenYiRan123456的博客 1227

Dify 部署与使用

本文介绍了Dify社区版的快速部署指南,涵盖系统架构、环境配置和初始化流程。Dify采用微服务架构,通过Docker Compose一键部署8个核心容器,包括前端、后端、数据库等组件。部署过程包括克隆代码、配置环境变量和启动服务,首次启动需3-10分钟下载镜像。文章详细讲解了.env文件的配置要点,包括服务地址、数据库连接和向量存储设置,并提供了生产环境安全加固建议。最后通过访问/install页面完成管理员账号初始化,即可开始使用Dify构建AI应用。

海兰 592

二分查找题目汇总

二分查找题目汇总

雪糕cool的博客 2046

SAP BTP ABAP RAP 开发对象命名规范与落地实践

摘要:SAP BTP ABAP开发中的命名规范实践 在企业级ABAP开发中,命名规范是提升交付效率的工程化手段。SAP推荐采用[/<namespace>/][<prefix>]_<object_name>_[<suffix>]的结构化命名框架,解决多对象、多团队的协同问题。关键实践包括:使用组织专属命名空间避免冲突;通过prefix区分同类型对象的不同语义(如UI_/API_);object_name体现业务语义而非技术细节;suffix表达次级差异。在具体对

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 1435

Pointnet++环境配置(Windows11和ubuntu)及训练教程

PS:前两行命令是数据处理的命令,主要是进行数据读取和生成相应的训练数据格式,在Ubuntu上是正常运行的,在Windows上运行会失败,无法生成训练用的数据格式,所以最好在Ubuntu上训练,如果一定要用Windows,可以ubuntu上生成数据格式在导到Windows上,数据生成不需要安装torch。通过anaconda配置的环境,在官网找的命令安装的,版本是2.1.0+cu118。训练命令:我使用的pycharm,直接在pycharm中打开终端,运行命令。Python版本:3.7。

AIcurator的博客 2558

二分查找经典例题

二分查找经典例题

weixin_62445683的博客 311

C3 模块与普通 Bottleneck 堆叠区别

维度普通 Bottleneck 堆叠C3 模块设计理念残差学习CSP + 特征分治信息路径单一路径双路径(主干 + bypass)融合方式相加(Add)拼接(Concat)+ 卷积计算效率较低高(通道减半)特征保留弱(多次非线性)强(直连保留原始信息)梯度传播单一主路径多样化、更稳定任务适配通用(分类为主)专为目标检测优化可扩展性有限极强(即插即用各种模块)实际性能基准线显著优于(尤其在小目标和效率方面)

浩瀚之水的专栏 107

二分查找完全指南(基础进阶)

二分查找是一种高效的搜索算法,适用于有序数组,时间复杂度为O(logn)。基本框架包括初始化指针、循环条件和边界更新,关键要注意循环终止条件和中间值计算方式。对于含重复元素的数组,进阶应用可以查找目标值的最左或最右边界,通过调整边界更新逻辑实现。虽然二分查找原理简单,但实际应用中容易在边界条件、重复元素处理上出错。此外,二分查找在单链表等非随机访问结构中的实用性较低。掌握这些核心要点能有效提升算法应用能力。

2401_82831477的博客 376

ofd转pdf报错:org.ofdrw.reader.ZipUtil.unZipFileByApacheCommonCompress【已解决】

ofd转pdf报错:java.lang.NoClassDefFoundError:org/apache/commons/io/function/IOIterator at org.ofdrw.reader.ZipUtil.unZipFileByApacheCommonCompress,java.lang.NoSuchMethodError:org.apache.commons.compress.archivers.zip.ZipArchiveInputStream.getNextEntry()

黄团团的个人博客 1283

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

数组的排序和二分查找

冒泡、选择、插入排序、二分查找,二分查找是建立在数组已经排好序的基础上。

weixin_64153571的博客 214

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

深究二分查找算法:从普通到进阶

本系列遵循由实践—理论—实践的过程,首先通过一些经典题型来认识、引出算法,然后再详细讲解该算法核心逻辑以及使用方法等,最后再回到实践中用该算法解决具体问题。 本篇主要介绍经典的二分算法。

Kaisir777的博客 803

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

【二分查找】--- 进阶题目赏析

二分过程:参考代码: 🏠 寻找旋转排序数组中的最小值 📌 题目内容 二分流程:参考代码: 思考:我们划分两端区间是以D为参考点,那么我们是否能以A为参考点呢? 🏠 0~n-1中缺失的数字 📒 思路二:直接遍历找结果 📒 思路三:位运算 📒 思路四:高斯求和公式 📒 思路五:二分查找 前面的思路都很简单,但时间复杂度都是O(N)。仔细观察我们发现因为缺失了数字,会造成二段性。参考代码:

2301_79448270的博客 1702

如何高效掌握LeetCode解题:从基础到进阶的完整指南

LeetCode题解项目(gh_mirrors/le/leetcode)是一个全面记录解题 journey 的开源项目,包含了大量经典算法问题的详细解析和可视化图表,帮助开发者系统提升算法能力。无论你是算法新手还是有经验的开发者,这个项目都能为你提供清晰的解题思路和实用的编程技巧。 ## 为什么选择这个LeetCode解题项目? 对于算法学习而言,仅仅知道答案是远远不够的。这个项目的核心价值在

gitblog_00471的博客 1050

< 算法基础 之 二分查找 >

在开发中,我们常常会需要查找某个顺序存储结构中的内容时,我们常常会直接使用Javascript中已经封装好的函数去查找或者辅助查找。但是也由于封装的特性,我们对这种封装好的函数,比较难以控制其复杂度,包括其内部执行语句的庸余部分难以把控。所以,在算法中,诞生了二分查找这个算法概念。二分查找(Binary Search)也叫作折半查找。二分查找有两个要求,一个是数列有序,另一个是数列使用顺序存储结构(比如数组)。

一个平平无奇的技术宅 684

高效编程从这里开始:掌握二分查找的艺术

最新接入DeepSeek-V3模型,点击下载最新版本InsCode AI IDE 高效编程从这里开始:掌握二分查找的艺术 在计算机科学领域,算法是程序员的核心技能之一。而作为经典算法之一的二分查找(Binary Search),以其高效的搜索性能和广泛的适用场景,成为每个开发者都必须掌握的重要工具。然而,对于初学者来说,理解和实现二分查找可能并非易事。本文将通过实际案例,展示如何利用智能化的开发工...

MoonbeamFalcon67的博客 862

优化二分查找算法:从理论到实践的高效编程之旅

最新接入DeepSeek-V3模型,点击下载最新版本InsCode AI IDE 优化二分查找算法:从理论到实践的高效编程之旅 在计算机科学中,二分查找(Binary Search)是一种高效的搜索算法,适用于已排序的数据集。其基本思想是通过反复将搜索区间减半,从而快速定位目标元素。尽管二分查找的概念相对简单,但在实际应用中,实现一个高效、无误的二分查找算法却并非易事。本文将探讨如何利用现代化的A...

inscode_031的博客 785

二分查找全解析:从基础到进阶

34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode)二分查找类的题目,有三个模版可以用,不需要背,理解就好1.朴素二分查找朴素二分查找跟那道二分查找的算法题差不多2.左端二分查找以下面这个例子来举例查找左边界时,我们按照原本的思路,可以将mid的取值范围分为三个区域但结合例子来分析,可以发现查找左边界时mid>=target是一种情况因此分为以下两种情况还有两个细节问题需要注意如下3.右端二分查找右边界的分析和左边界差不多,这里只给出结论。

xxxiugou123的博客 246

你真的了解二分查找吗?二分查找:你不知道的细节——从基础到变形

二分查找是一种在有序数组中高效查找元素的算法,其核心逻辑是每次比较中间元素与目标值,从而排除一半的搜索范围。该算法的时间复杂度为对数级O(log n),远优于线性查找的O(n)。二分查找的前提是数组必须有序且支持随机访问。基本实现包括初始化左右指针、循环比较中间元素并调整搜索范围。进阶应用包括查找左边界(第一个大于等于目标值的位置)和右边界(最后一个小于等于目标值的位置)。常见错误包括边界条件处理不当、区间定义混淆和溢出问题。二分查找还可用于求平方根、查找旋转数组最小值等场景。

1203

数据结构与算法:16 | 二分查找(下):二分查找进阶

文章目录变体一:查找第一个值等于给定值的元素变体二:查找最后一个值等于给定值的元素变体三:查找第一个大于等于给定值的元素变体四:查找最后一个小于等于给定值的元素解答开篇内容小结课后思考 变体一:查找第一个值等于给定值的元素 int bSearch(vector<int> &vec, int n, int value){ int beg = 0; int end = n - 1; while(beg <= end){ int mid = b

zj 2699

二分查找之进阶(插值查找)

Interpolation Search 1. 二分查找即是折半查找,即把数据分成2份进行比较判断。(前提是有序列表) 二分查找代码 function bsearch (list,item) { //有序队列的二分查找 let lindex = 0 let hindex = list.length-1 let mid while (lindex<=hindex) { mid = Math.floor(lindex+(hindex-lindex)/

weixin_51364599的博客 1万+
上一篇: C++ - 类和对象 #取地址运算符重载 #构造函数 #类型转换 #友元 #static 成员 #内部类 #匿名对象 #对象拷贝时编译器的优化
下一篇: leetcode - 前缀和
秦少游在淮海
博客等级 码龄3年 1071粉丝 88原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值