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

文章目录

  • 前言
  • 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) {// 终止条件  注意这里是ifif (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 + 1return 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.mzph.cn/news/84390.shtml

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

相关文章

halcon算子2、gray_histo

gray_histo 计算直方图 原形&#xff1a;gray_histo(Regions, Image : : : AbsoluteHisto, RelativeHisto) 功能&#xff1a;计算直方图 参数&#xff1a;Regions&#xff1a;区域&#xff0c;要计算的区域&#xff08;在image上的区域&#xff09; Image &#xff1a;要计算的…

Postman应用——Variable变量设置(Global、Environment和Collection)

文章目录 Global变量设置Environment变量设置Collection变量设置Global、Environment环境变量预览 Global、Environment和Collection变量使用&#xff0c;点击查看。 Global变量设置 全局变量设置&#xff0c;作用域是所有Collection、Folder和Request&#xff0c;全局变量只有…

Docker网络问题:容器无法访问外部网络

Docker网络问题&#xff1a;容器无法访问外部网络 &#x1f61f; Docker网络问题&#xff1a;容器无法访问外部网络 &#x1f61f;摘要 &#x1f914;引言 &#x1f310;正文 &#x1f913;为什么容器无法访问外部网络&#xff1f; &#x1f615;1. 网络配置错误2. 防火墙设置3…

【智能电表数据接入物联网平台实践】

智能电表数据接入物联网平台实践 设备接线准备设备调试代码实现Modbus TCP Client 读取电表数据读取寄存器数据转成32bit Float格式然后使用modbusTCP Client 读取数据 使用mqtt协议接入物联网平台最终代码实现 设备接线准备 设备调试 代码实现 Modbus TCP Client 读取电表数…

pymysql执行非查询语句会自动提交事务,关闭事务自动提交

一、前置条件 在mysql数据库生成数据&#xff1a; CREATE DATABASE mydatabase;CREATE TABLE Course (CourseID INT PRIMARY KEY,CourseName VARCHAR(100),Instructor VARCHAR(100),Credits INT,StudentID INT,FOREIGN KEY (StudentID) REFERENCES StudentInformation(Studen…

win10 Baichuan2-7B-Chat-4bits 上部署 百川2-7B-对话模型-4bits量化版

搞了两天才搞清楚跑通 好难呢,个人电脑 win10 ,6GB显存 个人感觉 生成速度很慢,数学能力不怎么行 没有ChatGLM2-6B 强,逻辑还行, 要求: 我的部署流程 1.下载模型 ,下载所有文件 然后 放到新建的model目录 https://huggingface.co/baichuan-inc/Baichuan2-7B-Chat-4bits/tr…

HarmonyOS之 组件的使用

一 容器 1.1 容器分类 Column表示沿垂直方向布局的容器。Row表示沿水平方向布局的容器。 1.2 主轴和交叉轴 主轴&#xff1a;在Column容器中的子组件是按照从上到下的垂直方向布局的&#xff0c;其主轴的方向是垂直方向&#xff1b;在Row容器中的组件是按照从左到右的水平方向…

怒刷LeetCode的第11天(Java版)

目录 第一题 题目来源 题目内容 解决方法 方法一&#xff1a;迭代 方法二&#xff1a;递归 方法三&#xff1a;指针转向 第二题 题目来源 题目内容 解决方法 方法一&#xff1a;快慢指针 方法二&#xff1a;Arrays类的sort方法 方法三&#xff1a;计数器 方法四…

如何借助上线初期运维管理守住项目建设最后一公里

随着运营商技术升级、业务发展&#xff0c;以及服务能力要求提升&#xff0c;当下新建项目的交付或系统大版本升级大多数都需要历经千辛万苦才达到上线的彼岸。然而&#xff0c;项目上线并不意味着项目结束&#xff0c;“上线”也并不意味着终点&#xff0c;而是一个新的管理模…

Redis学习笔记--001

Redis快速入门 文章目录 Redis快速入门一、初识Redis1.1、NoSQL数据库1.2、Redis介绍1.3、[Redis](https://redis.io/)的安装 二、Redis常见命令2.1、Redis默认启动2.2、指定配置启动2.3、Redis开机自启设置 三、Redis客户端3.1、Redis命令行客户端3.2、图形化桌面客户端 四、r…

软件项目开发的流程及关键点

软件项目开发的流程及关键点 graph LR A[需求分析] --> B[系统设计] B --> C[编码开发] C --> D[测试验证] D --> E[部署上线] E --> F[运维支持]在项目开发的流程中&#xff0c;首先是进行需求分析&#xff0c;明确项目的目标和功能要求。接下来是系统设计&am…

【Vue.js】vue-cli搭建SPA项目并实现路由与嵌套路由---详细讲解

一&#xff0c;何为SPA SPA&#xff08;Single Page Application&#xff09;是一种 Web 应用程序的开发模式&#xff0c;它通过使用 AJAX 技术从服务器异步加载数据&#xff0c;动态地更新页面内容&#xff0c;实现在同一个页面内切换不同的视图&#xff0c;而无需整页刷新 1.…

优维低代码实践:图片和搜索

优维低代码技术专栏&#xff0c;是一个全新的、技术为主的专栏&#xff0c;由优维技术委员会成员执笔&#xff0c;基于优维7年低代码技术研发及运维成果&#xff0c;主要介绍低代码相关的技术原理及架构逻辑&#xff0c;目的是给广大运维人提供一个技术交流与学习的平台。 优维…

爬虫 — App 爬虫(一)

目录 一、介绍二、APP 爬虫常见反爬三、APP 抓包常用工具四、模拟器五、安装 APP1、下载 APP2、安装 APP 六、fiddler1、工作原理2、安装3、基本介绍 七、环境配置1、fiddler 的配置2、夜神模拟器的配置 八、案例 一、介绍 爬虫分类——数据来源 1、PC 端爬虫&#xff08;网页…

Multisim14.0仿真(二十)74LS161 4位同步二进制加法计数器

一、仿真原理图&#xff1a; 二、仿真效果图&#xff1a;

云计算的未来:云原生架构和自动化运维的崭露头角

文章目录 云计算的演进云原生架构1. 容器化2. 微服务3. 自动化部署和扩展4. 故障恢复 自动化运维1. 基础设施即代码&#xff08;IaC&#xff09;2. 运维自动化示例&#xff1a;使用Ansible自动化配置管理 3. 自动化监控和报警 未来展望1. 更多的自动化2. 多云混合云3. 边缘计算…

《DevOps实践指南》- 读书笔记(九)

DevOps实践指南 25. 附录附录 1 DevOps 的大融合精益运动敏捷运动Velocity 大会运动敏捷基础设施运动持续交付运动丰田套路运动精益创业运动精益用户体验运动Rugged Computing 运动 附录 2 约束理论和核心的长期冲突附录 3 恶性循环列表附录 4 交接和队列的危害附录 5 工业安全…

如何向PDB文件添加双键

在用PDB文件进行分子绘图的时候&#xff08;制作OBJ&#xff09;&#xff0c;发现像Atomic blender插件和PDB本身并不支持双键&#xff0c;需要对PDB文件进行修改&#xff0c;参照的该yt链接https://www.youtube.com/watch?vYNoow7qkwFA&t364s&ab_channelEdvinFako 即…

由于找不到d3dx9_43.dll,无法继续执行代码要怎么解决

D3DX9_43.dll是一个动态链接库文件&#xff0c;它是DirectX的一个组件&#xff0c;主要用于支持一些旧版本的游戏和软件。当电脑缺少这个文件时&#xff0c;可能会导致这些游戏和软件无法正常运行。例如&#xff0c;一些老游戏可能需要D3DX9_43.dll来支持图形渲染等功能。此外&…

需求是怎么一步一步变态的

最初的需求 需求是处理一些数据&#xff0c;数据例子&#xff1a; 而界面要显示的样子&#xff1a; 看起来不太难&#xff0c;可以分解出需求&#xff1a; 每一列的所有数据要都能参与选择&#xff0c;或者输入当一个参数选中之后&#xff0c;比如选中A选中1&#xff0c;则…