算法通过村第九关-二分(中序遍历)黄金笔记|手撕二分

news2025/1/23 9:08:28

文章目录

  • 前言
  • 1. 基本查找
  • 2. 二分查找与分治
    • 2.1 循环的方式
    • 2.2 递归的方式
  • 3. 元素中的重复的二分查找
  • 总结


前言


提示:有些人,远看是灯塔,靠近是悬崖。 --任明信《别人》

二分查找是非常重要的算法之一,不仅要掌握,更要了解相关变形题目。

查找可以很简单,也可以很复杂,散列、动态规划等高难度算法都可以视为查找问题,这里我们先看看一些基础问题。

常见的查找算法有顺序查找、二分查找、插值查找,斐波那契查找、树表查找、分块查找、哈希查找等等。其实二分查找、插值查找以及斐波那契查找都可以归为一类----插值查找。插值查找和斐波那契额查找是在二分查找的基础上的优化查找算法。

这些算法中最重要的无疑是Hash查找和二分查找。Hash我们前面的章节已经接触过了,这里我们重点就来谈谈二分查找及其变形问题。

二分查找的价值不限于此,请记住凡是涉及到排好序的地方查找的都可以考虑采用二分查找来优化查找效率。不一定全局有序,只要某部分有序也行,针对这一部分进行二分查找,这是很多题目优化查找的重要途经。

除了二分外,还有插值查找、斐波那契查找,分块查找等多种常见的算法,本质上仍是对二分的进一步扩展。我们知道二分就是每次只取一半,但是在有些场景下,我们大致知道数据的位置了,就不必折半,取1/3,3/4这样不是也可以吗?当然可以,为了方便通用性,插值查找使用的公式是:

mid = low + (key - a[low])/(a[high] - a[low])*(high - low)

这个来源自牛顿发明的积分公式,如果不懂,可以找数学老师补补课🤔

分块查找是折半查找和顺序查找的一种改进方法,分块查找由于只要求索引表示有序的,对块内节点没有排序要求,因此特别适合节点动态变化的情况。分块查找要求把一个数据分为若干块,每一块里面的元素可以是无序的,但是块与块之间的元素需要是有序的。即第一块中的任意元素的关键字都必须小于第二块中的任意元素的关键字,第二块中的任意元素又都必须小于第三块中的任意元素,一次类推。

1. 基本查找

查找算法中顺序查找算是最简单的了,无论是有序还是无序的都可以,也不需要排序,只需要一个一个的对比就可以了,但是这样的话效率太低了,我们看下代码:

public static int search(int[] nums,int key) {
    for(int i = 0; i < nums.length; i++){
        if(nums[i] == key){
            return i;
        }
    }
    return -1;
}

顺序查找是最简单的一种查找算法,对数据的要求也随意,不需要排序即可查找。后面我们会介绍二分查找,插值查找、斐波那契查找都是基于已经排序过的数据。

2. 二分查找与分治

在计算机科学中,分治法是一种很重要的算法。字面上的解释就已经很通俗了,“分而治之”,就是把一个复杂的问题分成两个或者更多的相同或者相似的子问题,再把子问题分成更小的子问题…直到最后问题可以简单的直接求解,原问题的解即子问题的解的合并。这个技巧是很多高效算法的基础,比如二分搜索,排序算法(快速排序、并归排序等等…

任何一个可以用计算机求解的问题所需要的计算时间都与其规模有关。问题的规模越小,越容易直接求解,解题所需要的计算时间也越少。例如,对n个元素进行排序,当n=1时,不需要计算,当n= 2时只需要比较一次即可排序好,n = 3时只需要比较3次也可以知道结果…而当n较大时,问题就不是那么容易处理了。要想直接解决一个规模较大的问题,有时是相当困难的。

二分查找就是将中间结果与目标进行比较,一次去掉一半,因此二分查找可以说是最简单也最经典的分治了。
在这里插入图片描述
二分查找,不管是循环还是递归方式,我觉得都很重要,你要达到闭着眼1分钟写出来的地步。

这里补充一个问题,分治和递归是一回事吗?很显然不是。这两者完全不同的思想,二分查找是分治思想,我们可以采用递归或者循环来实现,但是很多递归问题不一定可以采用分治解决,因此完全不是一回事。

2.1 循环的方式

常见的使用循环的方式来实现二分查找如下,你打几分?

	/**
     * 循环实现二分查找
     *
     * @param array
     * @param low
     * @param high
     * @param target
     * @return
     */
    public static int binarySearch1(int[] array, int low, int high, int target) {
        // 循环遍历  小于等于
        while(low <= high){
            // 取中间值
            int mid =  (low + high) / 2;
            if (array[mid] == target){
                return mid;
            }else if  (array[mid] > target){
                // 由于array[mid]不是目标值,因此在下一次循环中需要排除
                high = mid - 1;
            }else{
                // 由于array[mid]不是目标值,因此在下一次循环中需要排除
                low = mid + 1;
            }
        }
        return -1;
    }

在具体的操作中可能又很多中形式,包括循环体中的high = mid - 1和low = mid + 1也有很多方式,这里需要与if后面的条件配合,我们不要给自己添麻烦,在理解的基础上熟记这种方式就可以了。

如果代码写成这样,可能刚刚及格(70),以为你有一个重要的细节没有处理,在计算机中除的效率是很低的,一半会采用位移的方式替代,也就是如下:

将:
    int mid = (low + high) / 2
改成:
    int mid = (low + high) >> 1;

如果这样的话,可以加10分,但是也是又问题的。面试官会继续问下去,你考虑到又什么问题嘛?这里又个漏洞☠️,就是low和high很大的话,low+high就可能溢出了,因此我们可以改成这样写:

int mid = low + (high - low) >> 1

你觉得这样就对了,大错特错🤕,你测试的时候就会出现问题,不信你试试。

因为这里的位移运算>> 优先级比加减要低,所以上面的代码就会变成这个样子:

int mid = (low + (high - low)) >> 1

很显眼,这个不可能达到正确结果的,解决方法也很简单,加上括号就可以了,所以我们的最终代码应该是这样:


    /**
     * 循环实现二分查找
     *
     * @param array
     * @param low
     * @param high
     * @param target
     * @return
     */
    public static int binarySearch1(int[] array, int low, int high, int target) {
        // 循环遍历  小于等于
        while(low <= high){
            // 取中间值   用左移代替除
            int mid =  low + ((high - low) >> 1);
            if (array[mid] == target){
                return mid;
            }else if  (array[mid] > target){
                // 由于array[mid]不是目标值,因此在下一次循环中需要排除
                high = mid - 1;
            }else{
                // 由于array[mid]不是目标值,因此在下一次循环中需要排除
                low = mid + 1;
            }
        }
        return -1;
    }

这样的话,面试官就提不出什么问题了,而且上面的这个优先级问题很多人只是理解了,但是没有做过测试,知其然不知所以然。因此很多面试官也不会注意这里会有死循环的情况。当然这里还没有考虑到元素重复的情况,这个以后讨论。

2.2 递归的方式

递归就很简单了,这里我们直接看代码:

  	/**
     * 方法二:递归方式实现
     *
     * @param array
     * @param low
     * @param high
     * @param target
     * @return
     */
    public static int binarySearch2(int[] array, int low, int high, int target) {
        // 终止条件  注意这里是if
        if (low <= high){
            // 提高
            int mid = low + ((high - low) >> 1);
            if (array[mid] == target){
                return mid;
            }else if(array[mid] > target){
                // 由于array[mid]不是目标值,因此在下一次循环中需要排除
                return binarySearch2(array, low, mid - 1, target);
            }else {
                // 由于array[mid]不是目标值,因此在下一次循环中需要排除
                return binarySearch2(array, mid + 1, high, target);
            }
        }
        // 表示没有找到
        return  -1;
    }	

这里的mid的计算和可能出现的问题与上面的是一样的。这也是面试体现基本功的亮点。如果写到这个程度的话,已经接近满分了,面试官也不会再说什么🥰。

3. 元素中的重复的二分查找

假如在上面的基础上,元素存在重复,如果重复则找左侧的第一个,请问该怎么处理?这也是快手以前出现过。

这里的关键是找到目标结果之后不是返回,而是继续向左移动。第一中,也是最简单的方式,找到相等的位置向左线性查找,直到找到相应的位置。

	/**
     * 重复元素二分找第一次出现的
     * @param nums
     * @param target
     * @return
     */
    public static int search1(int[] nums, int target) {
        if (nums == null || nums.length == 0){
            return -1;
        }
        int left = 0;
        // 特殊情况提前处理
        if (nums[0] == target){
            return 0;
        }
        int right = nums.length - 1;

        while(left <= right){
            int mid = left + ((right - left) >> 1);
            if (nums[mid] > target){
                right = mid - 1;
            }else if (nums[mid] < target){
                left = mid + 1;
            }else {
                // 注意判断 左移条件按
                while(mid != 0 && nums[mid] == target){
                    mid--;
                }
                // 思考一下这里为什么mid + 1
                return mid + 1;
            }
        }
        return -1;
    }

当然这里也可以使用递归的方法:(仅供参考 )


	/**
     * 方法二,仍然采用递归来找
     *
     * @param nums
     * @param target
     * @return
     */
    public static int search2(int[] nums, int target) {
        if (nums == null || nums.length == 0)
            return -1;
        return binSearch2(nums, target, 0, nums.length - 1);
    }
    
    private static int binSearch2(int[] nums, int target, int left, int right) {
        if (left > right)
            return -1;
        if (nums[left] == target)
            return left;
        int mid = left + ((right - left) >> 1);
        if (nums[mid] < target) {
            return binSearch2(nums, target, mid + 1, right);
        } else if (nums[mid] > target) {
            return binSearch2(nums, target, left, mid - 1);
        } else {
            return binSearch2(nums, target, left, mid);
        }
    }

注意这里找到之后while循环之后,为什么要mid+1,而不是返回mid?–此时这里nums[mid]已经不是target了,比如这个例子{1,2,2,2,2,3,3}。当target = 3 while循环后退出,nums[mid] = 2 所以这必须要返回mid + 1.

都写到这里,面试官可能还会给你加个餐🍗,假如重复数量特别大,此时是否可以对内层来一个二分呢?当然可以,我们可以找到相等的时候继续递归,找到目标之后根据需求继续递归寻找,这个可作为拓展⭐⭐⭐⭐⭐,思考一下。


总结

提示:二分查找;mid写法优化;重复值问题;查找算法;递归和分治

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

印章篆刻小程序商城的作用是什么

印章的需求度也有很高市场需求&#xff0c;处理办公印章外&#xff0c;还有艺术类的&#xff0c;而对爱好者来说&#xff0c;需要找到一家靠谱的品牌制作&#xff0c;包括材料、样式、内容等都有较高要求&#xff0c;线上可以接触到更多雕刻商家。 而对品牌来说&#xff0c;需…

常见应用层协议

一.HTTP&#xff08;超文本传输协议&#xff09; HTTP 和 HTTPS 二.FTP&#xff08;文件传输协议&#xff09; 三.SMTP&#xff08;简单邮件传输协议&#xff09; 四.POP3&#xff08;邮局协议版本3&#xff09; 五.IMAP&#xff08;互联网消息访问协议&#xff09; 六.DNS&am…

GitHub 基本操作

目录 1、创建库 2、上传项目 3、获取token 4、 删除库 1、创建库 登录github&#xff0c;点击“Create repository”创建库&#xff0c;点击右上方绿色的“New”&#xff0c;Repository name处填写你的项目名&#xff0c;可选“Add a README file"默认创建一个说明文…

基于SSM的保险业务管理系统设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

480万商品,如何架构商品治理平台?

说在前面 在40岁老架构师 尼恩的读者交流群(50)中&#xff0c;很多小伙伴拿高薪&#xff0c;完成架构的升级&#xff0c;进入架构师赛道&#xff0c;打开薪酬天花板。 最近有小伙伴拿到了一线互联网企业如京东、网易、微博、阿里、汽车之家、极兔、有赞、希音、百度、滴滴的架…

Fortinet 2023上半年全球威胁态势研究报告:勒索软件检测成下降趋势,针对性攻击持续升温

近日&#xff0c;专注于推动网络与安全融合的全球网络安全领导者Fortinet&#xff08;NASDAQ&#xff1a;FTNT&#xff09;&#xff0c;发布《2023上半年全球威胁态势研究报告》。报告显示&#xff0c;2023 年上半年勒索软件检出数量继续下降、高级持续性威胁&#xff08;APT&a…

机器人中的数值优化|【六】线性共轭梯度法,牛顿共轭梯度法

机器人中的数值优化|【六】线性共轭梯度法&#xff0c;牛顿共轭梯度法 往期回顾 机器人中的数值优化|【一】数值优化基础 机器人中的数值优化|【二】最速下降法&#xff0c;可行牛顿法的python实现&#xff0c;以Rosenbrock function为例 机器人中的数值优化|【三】无约束优化…

Spring Boot 3.0:构建下一代Java应用的新方法

文章目录 Spring Boot 3.0&#xff1a;一览1. **模块化应用程序**2. **更强大的自动化工具**3. **更强大的安全性**4. **更好的性能和扩展性** 如何开始使用Spring Boot 3.01. 安装Spring Boot CLI2. 创建新项目3. 配置应用程序4. 编写业务逻辑5. 测试应用程序6. 构建和部署 结…

DataGrip 恢复数据

DataGrip 恢复数据 准备数据库 create database chap02; use chap02;恢复数据 运行sql脚本方式恢复数据到刚刚创建的数据库 数据右键—> SQL Scripts —> Run SQL Script… 选择sql脚本

基本网络协议

TCP 协议 TCP 协议是一种传输控制协议&#xff0c;是一种面向连接的传输层协议,它提供高可靠性的通信高可靠性:数据无错误,数据无丢失,数据无失序,数据无重复到达。 TCP 协议头部结构 下图是 TCP 协议的头部结构&#xff0c;如图&#xff1a; 16 位端口号&#xff1a; 告知主机…

超声波乳化具有什么特点(优点)?

梵英超声(fanyingsonic)探针式超声波乳化棒 超声波乳化是通过探针式超声波探头&#xff0c;高强度超声波耦合到液体中并产生声空化。超声波或声空化产生高剪切力&#xff0c;提供将大液滴破碎成纳米尺寸液滴所需的能量。梵英超声(fanyingsonic)提供各种探头式超声波乳化棒和配件…

QT的安装和注册

浏览器搜索qt官网或者点链接直接跳转 Index of /new_archive/qthttps://download.qt.io/new_archive/qt/尽量下载5.6版本以上的 根据系统选择下载 进入qt官网 点击注册 在安装界面输入注册的账号 然后一直点击下一步 选择需要的组件 完成安装

基于springboot+vue的新闻推荐系统

目录 前言 一、技术栈 二、系统功能介绍 管理员模块的实现 用户信息管理 排行榜管理 新闻信息管理 用户模块的实现 首页信息 新闻信息 我的收藏 三、核心代码 1、登录模块 2、文件上传模块 3、代码封装 前言 随着信息互联网购物的飞速发展&#xff0c;国内放开了自…

MeterSphere 监控方案

前言&#xff1a;在部署MeterSphere之后&#xff0c;很多时候需要看下MeterSphere服务的监控信息&#xff0c;虽然有监控告警脚本&#xff0c;但还不是太直观&#xff0c;所以就结合 PrometheusExporterGrafana 部署一套完整的MeterSphere监控方案。 首先我们先罗列一下需要监控…

【工具使用】如何使用Audition创建多音轨项目并验证

一&#xff0c;简介 本文主要介绍如何使用Audition 2020创建多音轨工程&#xff0c;并新增轨道&#xff0c;供参考。 二&#xff0c;操作步骤 2.1 新建多轨工程 打开Audition&#xff0c;新建多轨会话&#xff1a; 设置参数&#xff0c;点击确定&#xff1a; 工程默认有6个…

MataDoor 模块化后门:先发现后解除

&#x1f441; PT 专家安全中心的专家发现了一个新的 Dark River 组织&#xff0c;该组织利用名为 MataDoor 的高科技模块化后门攻击国防工业公司。请阅读我们的最新研究。 我们的专家于 2022 年 10 月在调查一家俄罗斯工业企业的事件时首次遇到这种恶意软件。 &#x1f977;…

Javascript中生成器函数和Generator对象的介绍与使用

✨Generator &#x1f3b6;Generator的描述 Generator 函数是ES6提供的一种异步编程解决方案 &#x1f389;三种异步方法出现的顺序 &#x1f367;Generator的 核心语法 // 像下面这样 func 这样的函数称之为生成器函数 // 通过 * 进行声明 通过yield进行等待 function* fu…

十六,镜面IBL--预滤波环境贴图

又到了开心的公式时刻了。 先看看渲染方程 现在关注第二部分&#xff0c;镜面反射。 其中 这里很棘手&#xff0c;与输入wi和输出w0都有关系&#xff0c;所以&#xff0c;再近似 其中第一部分&#xff0c;就是预滤波环境贴图&#xff0c;形式上与前面的辐照度图很相似&#…

TensorFlow入门(五、指定GPU运算)

一般情况下,下载的TensorFlow版本如果是GPU版本,在运行过程中TensorFlow能自动检测。如果检测到GPU,TensorFlow会默认利用找到的第一个GPU来执行操作。如果机器上有超过一个可用的GPU,除第一个之外的其他GPU默认是不参与计算的。如果想让TensorFlow使用这些GPU执行操作,需要将运…

Java多线程编程-栅栏CyclicBarrier实例

前言 本文是基于《Java多线程编程实战指南-核心篇》第五章个人理解&#xff0c;源码是摘抄作者的源码&#xff0c;源码会加上自己的理解。读书笔记目前笔者正在更新如下&#xff0c; 《Java多线程编程实战指南-核心篇》&#xff0c;《How Tomcat Works》&#xff0c;再到《spr…