【算法】对二分搜索的理解

二分搜索大家都很熟悉,首先我们先来看看基本框架

func binarySearch(nums []int, target int) int {
    left, right := 0, ...

    for ... {
        mid := left + (right-left)/2
        if nums[mid] == target {
            ...
        } else if nums[mid] < target {
            left = ...
        } else if nums[mid] > target {
            right = ...
        }
    }
    return ...
}

看完代码之后我们会发现:

  1. 数组得是有序的才能成立
  2. 在存在两个及以上的答案时,只能找出一个,至于下标的情况我觉得可以自定义
  3. 如果数组长度为偶数,初始化时mid在中间偏左的那一格

接下来在实战中看:

寻找一个数

这可以说是最简单的内容

func binarySearch(nums []int, target int) int {
    left := 0 //
    right := len(nums) - 1 

    for left <= right {
        mid := left + (right - left) / 2 
        if nums[mid] == target { 
            return mid
        } else if nums[mid] < target { 
            left = mid + 1 // [mid + 1, right]
        } else if nums[mid] > target { // 目标值在左半部分,注意
            right = mid - 1 // [left, mid - 1]
        }
    }
    return -1 // 未找到
}

这里需要注意一点,为何是 for left <= right 而不是 for left < right

我们可以先从最极端的情况入手,假设要寻找的数在最后一个,那么在前一步应该是 left = right - 1, mid = left ,然后 left = mid + 1 = right,在进行循环时,循环就会进不去。

从理论上解释,因为在之前设定值时,数组为闭区间 [left, right] ,所以得保证边界值,也就是说,循环的条件设定和搜索区间设定是相关联的

寻找左侧边界

那么接下来看一下右侧开区间的做法:

func leftBound(nums []int, target int) int {
    left := 0
    right := len(nums)

    for left < right {
        mid := left + (right - left) / 2
        if nums[mid] == target {
            right = mid
        } else if nums[mid] < target {
            left = mid + 1 // [mid + 1, right)
        } else if nums[mid] > target {
            right = mid // [left, mid)
        }
    }
    return left
}

不难发现,随着区间的修改,在对应条件下的操作也会对应转换。

在这里阐述一点我的个人想法,二分查找的最大特点在于区间设定,而在对应条件下做出的操作有点像递归,基本盘并没有改变

寻找右侧边界

func right_bound(nums []int, target int) int {
    left, right := 0, len(nums)
    
    for left < right {
        mid := left + (right-left)/2
        if nums[mid] == target {   
            left = mid + 1        
        } else if nums[mid] < target {
            left = mid + 1
        } else if nums[mid] > target {
            right = mid
        }
    }
    return left - 1  
}

这里面的精华在于

if nums[mid] == target {   
	 left = mid + 1 

他并没有直接的收网,而是继续直到最右侧的诞生,这也得益于mid的设置,可以把他看成以left为基准向前进

实战

我们来看力扣的34. 在排序数组中查找元素的第一个和最后一个位置

直接把方法摆上既可以解决

func searchRange(nums []int, target int) []int {
    left := leftBound(nums, target)
    right := rightBound(nums, target)
    return []int{left, right}
}

func leftBound(nums []int, target int) int {
    left, right := 0, len(nums)-1
    for left <= right {
        mid := left + (right - left) / 2
        if nums[mid] < target {
            left = mid + 1
        } else if nums[mid] > target {
            right = mid - 1
        } else if nums[mid] == target {
            // 别返回,锁定左侧边界
            right = mid - 1
        }
    }
    // 判断 target 是否存在于 nums 中
    if left < 0 || left >= len(nums) {
        return -1
    }
    // 判断一下 nums[left] 是不是 target
    if nums[left] == target {
        return left
    }
    return -1
}

func rightBound(nums []int, target int) int {
    left, right := 0, len(nums)-1
    for left <= right {
        mid := left + (right - left) / 2
        if nums[mid] < target {
            left = mid + 1
        } else if nums[mid] > target {
            right = mid - 1
        } else if nums[mid] == target {
            // 别返回,锁定右侧边界
            left = mid + 1
        }
    }
    // 判断 target 是否存在于 nums 中
    // if left - 1 < 0 || left - 1 >= len(nums) {
    //     return -1
    // }

    if right < 0 || right >= len(nums) {
        return -1
    }
    if nums[right] == target {
        return right
    }
    return -1
}

当然也有另外一种解法,力扣显示这种时间复杂度更低
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/225024.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

快速安装Axure RP Extension for Chrome插件

打开原型文件的html&#xff0c;会跳转到这个页面&#xff0c;怎么破&#xff1f; 我们点开产品设计的原型图如果没有下载Axure插件是打不开&#xff0c;而我们国内网通常又不能再google商店搜索对应插件&#xff0c;下面教大家如何快速安装 1、打开原型文件->resources-&g…

星钻图形输出

答案&#xff1a; #include <stdio.h> int a 0, b 0; void printLine(int a , int b) //输出一行包含&#xff1a;若干个空格 若干个*&#xff0c;第一&#xff0c;二个参数为空格数和*数&#xff1b; (定义一个星钻输出函数) {while (a--) //打印a个空格{printf(…

内衣洗衣机和手洗哪个干净?高性价比内衣洗衣机推荐

通常来说&#xff0c;我们的内衣裤对卫生要求比较高&#xff0c;毕竟是贴身穿的&#xff0c;所以如果和一般的衣物一起洗&#xff0c;就怕会有细菌互相感染。所以很多用户为了内衣裤的卫生都会选择自己手动洗&#xff0c;但手洗一方面很费时间和人力&#xff0c;另一方面又很伤…

el-menu标题过长显示不全问题处理

项目基于vue-element-admin 问题 期望 处理方式 \src\layout\components\Sidebar\index.vue 文件后添加CSS <style scped> /* 侧栏导航菜单经典 文字超长溢出问题 CSS折行 */ .el-submenu__title {display: flex;align-items: center; } .el-submenu__title span {white-…

刚毕业做前端开发难度会很大吗

这里是up主的三段经历&#xff0c;小伙伴们可以自己看一看 首先可以确定的是&#xff0c;不是很大&#xff0c;因为如果你能走到做开发这一步去&#xff0c;说明你的基础能力是够硬的 我的第一段经历&#xff0c;也就是我的第一份实习&#xff0c;是一家小企业&#xff0c;一共…

单片机第三季-第六课:STM32标准库

1&#xff0c;为什么会有标准外设库 传统单片机软件开发方式&#xff1a; (1)芯片厂商提供数据手册、示例代码、开发环境&#xff1b; (2)单片机软件工程师面向产品功能&#xff0c;查阅数据手册&#xff0c;参考官方示例代码进行开发&#xff1b; (3)硬件操作的方式是用C语言…

软件设计中如何画各类图之六状态图:生动呈现对象生命周期状态转换的重要工具

目录 1 状态图简介2 状态图的符号及说明2.1 状态&#xff08;State&#xff09;2.2 转移&#xff08;Transition&#xff09;2.3 起始状态与终止状态2.4 动作&#xff08;Action&#xff09; 3 画状态图的步骤3.1 确定对象3.2 定义状态3.3 标识转移3.4 标注动作3.5 添加起始和结…

万宾科技智能水环境综合治理监测系统效果

水环境综合治理是一项旨在全面改善水环境质量的系统工程。它以水体为对象&#xff0c;综合考虑各种因素&#xff0c;通过科学规划和技术手段&#xff0c;解决水环境污染、生态退化等问题&#xff0c;核心理念是“统一规划、分步实施&#xff1b;标本兼治&#xff0c;重在治本&a…

C#事件的本质

event字段本质就是对委托进行私有访问限制&#xff0c;事件的本质就是委托&#xff0c;只不过系统会对用event字段修饰的委托进行了特殊处理&#xff0c;比如自动生成一个私有的委托变量&#xff0c;添加两个事件访问器&#xff0c;同时禁止外部类对事件的Invoke等方法调用。 …

Spring Cache【娓娓道来】

目录​​​​​​​ 1.自我介好&#x1f633;&#x1f633;&#x1f633; 2.常用注解 &#x1f495;&#x1f495;&#x1f495; 3.EnableCaching&#x1f926;‍♂️&#x1f926;‍♂️&#x1f926;‍♂️ 4.CachePut&#x1f937;‍♀️&#x1f937;‍♀️&#x1f93…

虚拟数据优化器VDO

本章主要介绍虚拟化数据优化器。 什么是虚拟数据优化器VDO创建VDO设备以节约硬盘空间 了解什么是VDO VDO全称是Virtual Data Optimize&#xff08;虚拟数据优化)&#xff0c;主要是为了节省硬盘空间。 现在假设有两个文件file1和 file2&#xff0c;大小都是10G。file1和 f…

定时器的使用及实现

在Java中&#xff0c;定时器&#xff08;Timer&#xff09;是一个用于执行任务的工具类。它可以安排任务在指定的时间点执行&#xff0c;或者按照指定的时间间隔周期性地执行。 1. Timer类 Timer类位于java.util包中&#xff0c;它提供了一种简单而便利的方式来安排以后的任务…

AR + 通信,虚实结合让工作协同从线上到「现场」

在数字经济无所不在的当下&#xff0c;千行百业都与数智化办公接轨并因其实现转型升级。关注【融云 RongCloud】&#xff0c;了解协同办公平台更多干货。 升级的背后&#xff0c;是利用技术把工作用更自然的方式连接起来&#xff0c;让整个工作流协同更顺、体验更好。 而其中…

Dijkstra(迪杰斯特拉)算法

Dijkstra(迪杰斯特拉)算法的思想是广度优先搜索&#xff08;BFS&#xff09; 贪心策略。 是从一个顶点到其余各顶点的最短路径算法&#xff0c;节点边是不各自不同的权重&#xff0c;但都必须是正数 如果是负数&#xff0c;则需要 Bellman-Ford 算法 如果想求任意两点之间的距离…

占用站点资源,无法正常登录?这个功能帮助解决

在企业里随着PDM用户的增加PDM管理员是否发现原本的站点已经不够用出现部分用户占用站点资源导致其他用户无法正常登录导致该问题无法解决&#xff0c;本篇介绍PDM自动下线的功能助力企业解决问题&#xff0c;更好的帮助企业完成PDM的正常使用 今天我给大家带来的就是SOLIDWOR…

外网的maven项目转移到内网操作的步骤

1、新起一个仓库路径testRep&#xff0c;idea 引用的maven里的setting.xml里仓库配置修改成刚才建的路径&#xff0c;目的把需要的jar全部下载到那个文件夹里 2、项目打压缩包&#xff0c;刚才仓库文件夹打压缩包&#xff0c;并复制到内网电脑 3、内网电脑idea引入项目 4、修改…

【重点】【矩阵】48. 旋转图像

题目 参考答案 法1&#xff1a;辅助矩阵 class Solution {public void rotate(int[][] matrix) {int n matrix.length;int[][] newMatrix new int[n][];for (int i 0;i < n; i) {newMatrix[i] matrix[i].clone();}for (int i 0; i < n; i) {for (int j 0; j <…

PD-1、BRAF和MEK联合抑制BRAFV600E结直肠癌癌症的2期试验

今天给同学们分享一篇文章“Combined PD-1, BRAF and MEK inhibition in BRAFV600E colorectal cancer: a phase 2 trial”&#xff0c;这篇文章发表在Nat Med期刊上&#xff0c;影响因子为82.9。 结果解读&#xff1a; MAPK抑制增强BRAF V600E CRC的免疫反应 作者之前在BRAF…

图的深度优先搜索(数据结构实训)

题目&#xff1a; 图的深度优先搜索 描述&#xff1a; 图的深度优先搜索类似于树的先根遍历&#xff0c;是树的先根遍历的推广。即从某个结点开始&#xff0c;先访问该结点&#xff0c;然后深度访问该结点的第一棵子树&#xff0c;依次为第二顶子树。如此进行下去&#xff0c;直…

彩色成像的基础和应用 原理 Principles(一)

下面我将不定期尽可能出一系列&#xff08;我觉的非常好&#xff09;翻译的文章来解释颜色这们学科。【下图为此次翻译的书籍封面】 Introduction: 颜色是一种与光的物理学&#xff0c;物质的化学&#xff0c;物体的几何特性以及人…