二分查找,又名折半查找,其搜索过程如下:

  • 从数组中间的元素开始,如果元素刚好是要查找的元素,则搜索过程结束
  • 如果搜索元素大于或小于中间元素,则排除掉不符合条件的那一半元素,在剩下的数组中进行查找
  • 由于每次需要排除掉一半不符合要求的元素,这需要数组是已经排好序的或者是有规律可循的

在二分查找中,对于每一个反馈,我们能过滤掉一半的数据,因此二分查找相对于普通遍历是一个高效的查找算法,其时间复杂度为O(logn),虽然二分查找的思想很简单,然而二分查找的细节可不少,这里根据力扣的题型给出两种二分查找的写法

二分查找基础版

题目链接

使用二分查找的方式在一个升序的数组中找到指定的值并且返回,其代码框架如下:

定义left为左指针,right为右指针,搜索的区域在[left,right]中
搜索目标值为targtwhile(left<=right){//得到查找的中点mid=(left+right)/2//对于升序数组当前值小于目标值,目标值应该在当前值的右侧if(mid<target){左指针右移,如今搜索区域变为[mid,right]}else if(mid>target){右指针左移,如今搜索区域变为[left,mid]}else{只剩下等于的情况了,直接返回结果找到了目标值}
}

代码如下:

class Solution:def search(self, nums: List[int], target: int) -> int:left, right = 0, len(nums) - 1while left <= right:mid = (right - left) // 2 + leftnum = nums[mid]if num < target:left = mid + 1elif num > target:right = mid - 1else:return midreturn -1

注意,重点来了!!!注意代码中的几个关键点:

  • while循环中的条件是left<=right,这意味着在搜索不到答案退出循环时left=right+1,而在二分查找中我们查找的是[left,right]这个区间,这说明所有的数都被搜索到了,记住这一点,以下我们统称left<=right的这种写法为写法一
  • mid=(right-left)//2+left这个写法其实与mid=(left+right)/2效果是一致的,这么写的目的是为了防止整形溢出的情况,因为left+right相加如果大于整形最大值的话会导致结果异常
  • 在移动左右指针时使用的是right=mid-1和left=mid+1,这是因为mid的值已经在上一轮循环中搜索过了,不需要再进行一次搜索

二分查找进阶版--寻找左右边界

为了引出二分查找的第二种写法,这里给出一道非力扣题型的二分查找

寻找左侧边界

例如对于数组[1,2,2,2,3],查找目标为2,我们需要返回最左边的2的下标1,如果使用写法一我们的代码看起来是下面这样的:

class Solution:def search(self, nums: List[int], target: int) -> int:left, right = 0, len(nums) - 1while left <= right:mid = (right - left) // 2 + leftnum = nums[mid]if num < target:left = mid + 1elif num > target:right = mid - 1else:right = mid - 1if(left >= len(nums) || nums[left] != target): return -1return left

这段代码有两个注意点:

  • 由于要找到最左侧的边界,在else条件里(num=target), 我们不再直接返回结果,而是继续的收缩右边界,直到right越界或者找到最左边界坐标
  • 是不是决定很奇怪,为什么最后返回的是left,而且是对left做越界判定,记不记得我在之前说过left<=right的退出条件是left=right+1,我们复盘一下最后一轮循环的情况,你就知道为什么最后返回的是left而且也是对left进行越界的判断了
  • 复盘最后一轮循环的过程,对于数组[1,2,2,2,3],target为2,假设当前mid为1,循环也接近了极限,也就是left=mid=right=1,根据代码,在num=target=2的时候,根据代码right=mid-1,那么最后right是否一定不指向正确的结果1,而left指针还没有被移动,依然指向mid,所以我们最后应该返回left
  • 再来看一种情况对于数组[1,2,2,2,3]如果我们查找的是数字4,由于数组中的数字都小于target,我们会不断的缩小左边界,直到退出循环left=right+1,此时left一定是越界的,因为right从头到尾都没动过,而left=right+1保证了left越界
  • 再来看一种情况对于数组[1,2,2,2,3]如果查找的是数字0,由于数组中的数字都大于target,我们会不断的缩小右边界,直到退出循环left=right+1,由于我们最后判断的是left指针,即使right指针越界了,left=right+1也能保证left刚好在下标0的位置,所以是不需要考虑左侧越界的情况的

讲到这里是不是觉得写法一来解决这样的问题真的很复杂,所以我们接下来要说写法二

写法二

使用写法二来解决左侧边界问题,代码如下:

class Solution:def search(self, nums: List[int], target: int) -> int:left, right = 0, len(nums) - 1while left < right:mid = (right - left) // 2 + leftnum = nums[mid]if num < target:left = mid + 1else:right = midreturn left

注意点如下:

  • while循环结束的条件是left<right,这说明循环退出的条件是left==right==mid,最后的返回值我们这里返回的是left,其实返回right也是可以的,因为循环退出的条件是left==mid==right嘛
  • 可以看到另一个不同在于在else条件里我们偏移右指针的时候写的是right=mid,我们合并了num>target和num=target的条件,所以为什么是写成right=mid而不是right=mid-1呢?

left=mid+1和right=mid

  • 对于left=mid+1可以这么理解,mid此时的值一定不是我们需要的值,所以我们可以直接越过.举个例子对于数组[1,2,3,4,4,5,5],如果要搜索数字5,我们第一个搜索到的数字mid=(left+right)/2=nums[3]=4,此时4<target,那么是否一定不是我们需要的结果,那么我们可以直接越过
  • 对于right=mid,mid的值可能是我们需要的值,但是在当前循环中我们无法判断出来,所以我们进行保留,举个列子,对于数组[1,5,5,5],如果要搜索数字5,我们第一个搜索到的数字mid=nums[1]=5,此时的5是下标为1的5,并且也是最终我们需要找到的最左侧边界,此时right=mid这行代码就能帮助我们把结果保留了,而后等待left不断右移知道left==right就能退出循环返回最终结果了

寻找右侧边界

学会了寻找左侧边界,那么右侧边界是否也很容易理解了,其实这里有一个小坑,还需要填一下,这里先给出寻找右侧边界的代码

class Solution:def search(self, nums: List[int], target: int) -> int:left, right = 0, len(nums) - 1while left < right:mid = (right - left + 1) // 2 + leftnum = nums[mid]if num > target:right = mid - 1else:left =  midreturn right
  • 相信你已经发现了,在取mid的时候我们在括号里加上了一个1,这是因为程序语言中除法操作后下标向下取整(例如(1+2)/2=1),而在寻找右侧边界的过程中,这个操作会导致死循环,永远退不出循环,举个列子:
  • 两者相除,向下取整,这个操作是不是说明mid在有些情况下会向左偏向,我们在寻找左侧边界的时候,需要它向左偏向因此能退出循环,而在寻找右侧边界时,需要它向右收缩边界,此时是否就和向下取整这个情况矛盾了,此时一左一右就会导致永远进入死循环了,因此需要mid=(right-left+1)//2+left这个操作来打破这个死循环

总结

这里很重要哦,如果理解了这里,就离彻底理解二分写法不远了

  • 写法一适合用于在[left,right]这个范围内找到值,例如最基础的二分写法,这种写法需要判断最后返回的是left还是right,要分析最后一次循环后造成的结果
  • 写法二适合用于在[left,right]这个区间内排除值,然后在left=right=mid的时候找到最后的答案,因此写法二的if条件里写的是target值一定不存在的情况
  • 在写法二中如果else条件里是left=mid这个条件(趋向是收缩左边界和向下取整相反),需要修改mid为(right-left+1)//2+left来避免死循环情况的出现

二分搜索代码很简单,但细节是魔鬼,如果觉得自己理解了可以使用写法二做做这一题,题目链接

算法小抄6-二分查找相关推荐

  1. labuladong的算法小抄pdf_真漂亮!这份GitHub上爆火的算法面试笔记,助你圆满大厂梦...

    前言 Github作为程序员们的后花园,一直以来都是程序员最喜欢逛逛.学习的地方,小编也不例外,最近看到一份对标BAT等一线大厂的算法面试笔记,已经标星68+K了,很是惊讶,看了一下,觉得知识点整理得 ...

  2. Lubuladong算法小抄思考和题集

    0.引言 本文主要针对Labuladong算法小抄中设计的LeetCode提集进行整理,并给出相关题目解法.小编欢迎大佬指出其中问题,可随时通过邮件联系小编:xiakexiaohu#163.com. ...

  3. 关于“labuladong的算法小抄”的学习笔记---第0章核心框架汇总的后半部分技巧(c++版)

    目录 前言 一.回溯算法秒杀所有排列/组合/子集问题 回溯和DFS之间区别---遍历树枝or遍历节点 1.子集(元素无重不可复选) 2.组合(元素无重不可复选) 3.排列(元素无重不可复选) 4.子集 ...

  4. 赠书:京东当当新书榜TOP1的“算法小抄”!

    上周日,一本算法新书空降京东和当当双网计算机新书榜榜首, 在大家还一脸蒙圈的时候, 它又迅猛夺下京东全品类新书榜第一名, 以及京东计算机图书总榜第一名, 并且,在两网榜单上霸榜至今! (京东计算机总榜 ...

  5. 这本空降京东当当新书榜TOP1的“算法小抄”是什么来头?

    点击上方蓝色"程序猿DD",关注我 每周福利送不停! 上周日,一本算法新书空降京东和当当双网计算机新书榜榜首, 在大家还一脸蒙圈的时候, 它又迅猛夺下京东全品类新书榜第一名, 以及 ...

  6. labuladong的算法小抄pdf_随机算法:水塘抽样算法

    读完本文,你可以去力扣拿下如下题目: 382.链表随机节点 398.随机数索引 -----------我最近在 LeetCode 上做到两道非常有意思的题目,382 和 398 题,关于水塘抽样算法( ...

  7. labuladong 的算法小抄_关于算法笔试的几个套路,一点就透

    以下文章来源于labuladong ,作者labuladong 我知道各位是被标题吸引进来的,那就不废话,先说几个算法笔试的硬核套路,再说说语言选择和做题复习的策略. 避实就虚 大家也知道,大部分笔试 ...

  8. labuladong 的算法小抄_来自GitHub 68.8k star的硬核算法教程

    很多朋友害怕算法,其实大可不必,算法题无非就那几个套路,一旦掌握,就会觉得算法实在是太朴实无华且枯燥了! 本文选自硬核算法教程<labuladong的算法小抄>,带你学习套路,把握各类算法 ...

  9. php二分查找算法时间复杂度,一个运用二分查找算法的程序的时间复杂度是什么...

    一个运用二分查找算法的程序的时间复杂度是"对数级别".二分查找是一种效率较高的查找方法,算法复杂度即是while循环的次数,时间复杂度可以表示"O(h)=O(log2n) ...

最新文章

  1. 哈尔滨理工大学ACM集训第二周总结
  2. python3.6 3.7共存_[转]CentOS 7安装Python3.6过程(让linux系统共存Python2和Python3环境)...
  3. Netty在IDEA中搭建HelloWorld服务端并对Netty执行流程与重要组件进行介绍
  4. 小米新机将搭载鸿蒙,小米新機將搭載鴻蒙係統?還得等鴻蒙進一步的消息!
  5. 文献记录(part55)--基于分布式非负矩阵分解的大规模主题社区挖掘
  6. 在java中生成二维码,并直接输出到jsp页面
  7. 搜索树判断 (25 分)(先序建立二叉树)
  8. Regex Tester 安装教程
  9. java 值传递 引用传递的理解 言简意赅 一字千金
  10. python的基本数据类型关键字_Python3 基本数据类型
  11. 考研英语——长难句语法
  12. 除了性以外,有没有快速、高效的释放压力、清空大脑的方式?
  13. Android 点击屏幕空白处隐藏软键盘
  14. Linux学习之——/etc/sysconfig目录
  15. 快速余弦变换matlab,离散余弦变换 - MATLAB Simulink - MathWorks 中国
  16. 金山代码高人,雷军的前辈,虽退隐江湖依然是传说
  17. Windows 11 正式版最低配置要求来了,你的电脑支持吗?
  18. MyEclipse10 + Axis2 开发webservice
  19. phpstudy安装及使用教程
  20. 节点电压法解复杂反馈的op电路

热门文章

  1. c语言mfc写贪吃蛇,手把手教你用MFC编写贪吃蛇.doc
  2. u盘复制到计算机的文档打不开怎么办,复制到U盘的文件打不开怎么办
  3. VMware虚拟机安装centos7,登录时输入密码出现抱歉,没有奏效,请再试一遍。
  4. 数学建模算法总结——04层次分析法
  5. 控件拖动后,某些事件引起的布局重置或位置还原问题
  6. 【无标题】使用Oracle官方提供的ova文件建立Oracle 19c学习环境
  7. java后端+uniapp 对接微信app支付 报错-1
  8. 不想上班,又想挣钱怎么办
  9. android shap,Android中Shape的用法详解
  10. Redis学习视频教程