当前位置: 首页 > news >正文

二分查找及其变种

一、概念

二分查找算法(Binary Search Algorithm)是一种在有序数组中查找特定元素的高效搜索方法。

其基本思想是将目标值与数组中间的元素进行比较,如果目标值等于中间元素,则查找成功;如果目标值小于中间元素,则在数组左半部分继续查找;如果目标值大于中间元素,则在数组右半部分继续查找。这个过程将不断重复,直到找到目标值或搜索范围为空为止。

二、实现步骤

2.1 初始化: 确定搜索范围的左右边界,通常用两个指针表示,一个指向数组的起始位置(left,索引0),另一个指向数组的结束位置(right,数组长度减1)。

2.2 循环条件: 当左指针小于等于右指针时,继续查找。

2.3 计算中间索引: 计算当前搜索范围的中间位置 mid,通常使用 (left + right) / 2 来避免整数溢出。

口诀:奇数二分取中间 偶数二分取中间靠左;

2.4 比较中间元素: 比较 array[mid] 与目标值 target:

  • 如果 array[mid] 等于 target,则查找成功,返回 mid。
  • 如果 array[mid] 大于 target,则在左半部分继续查找,更新右指针 right = mid - 1。
  • 如果 array[mid] 小于 target,则在右半部分继续查找,更新左指针 left = mid + 1。

2.5 结束循环: 如果左指针大于右指针,表示搜索范围为空,目标值不在数组中,返回一个表示未找到的值,如 -1。

三、代码实现

public class BinarySearch {public int binarySearch(int[] nums, int target) {int left = 0;int right = nums.length - 1;while (left <= right) {int mid = left + (right - left) / 2; // 防止溢出if (nums[mid] == target) {return mid; // 找到目标值,返回索引} else if (nums[mid] < target) {left = mid + 1; // 在右半部分查找} else {right = mid - 1; // 在左半部分查找}}return -1; // 未找到目标值,返回-1}
}

注意事项:

 1. 如果left 和 right比较大的话,两者之和就有可能会溢出。改进的方法是将 mid 的计算方式写成 left +(right-left )/2。更进一步,如果要将性能优化到极致的话,我们可以将这里的除以 2 操作转化成位运算 left +((right-left )>>1)。因为相比除法运算来说,计算机处理位运算要快得多。

2. while  (left < = right),注意括号内为 left <= right ,而不是 left < right ,如果我们设置条件为 left  <  right 则当我们执行到最后一步时,则我们的 left 和 right 重叠时,则会跳出循环,返回 -1,区间内不存在该元素,但是不是这样的,我们的 left 和 right 此时指向的就是我们的目标元素 ,但是此时 left = right 跳出循环

3. left = mid + 1,right = mid - 1 而不是 left = mid 和 right = mid。当我们的target 元素为 16 时,然后我们此时 left = 7 ,right = 8,mid = left + (right - left) = 7 + (8-7) = 7,那如果设置 left = mid 的话,则会进入死循环,mid  值一直为7 。

四、算法变种

4.1 

给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。

如果数组中不存在目标值 target,返回 [-1, -1]。

示例 1:
输入:nums = [5,7,7,8,8,10], target = 8 输出:[3,4]示例 2:
输入:nums = [5,7,7,8,8,10], target = 6 输出:[-1,-1]示例 3:
输入:nums = [], target = 0 输出:[-1,-1]

从最原始的二分查找代码中分析, nums[mid] == target 时则返回,nums[mid] < target 时则移动左指针,在右区间进行查找, nums[mid]  >  target 时则移动右指针,在左区间内进行查找。

那么我们思考一下,如果此时我们的 nums[mid] = target ,但是我们不能确定 mid 是否为该目标数的左边界,所以此时我们不可以返回下标。

 

此时 mid = 4 ,nums[mid] = 5,但是此时的 mid 指向的并不是第一个 5,所以我们需要继续查找 ,因为我们要找的是数的下边界,所以我们需要在 mid 的值的左区间继续寻找 5 ,那应该怎么做呢?

我们只需在target <= nums[mid] 时,让 right = mid - 1即可,这样我们就可以继续在 mid 的左区间继续找 5 。

解决方案:

将小于和等于合并在一起处理,当 target <= nums[mid] 时,我们都移动右指针,也就是 right = mid -1,还有一个需要注意的就是,我们计算下边界时最后的返回值为 left ,当上图结束循环时,left = 3,right = 2,返回 left 刚好时我们的下边界。

计算上边界时算是和计算上边界时条件相反,

计算下边界时,当  target <= nums[mid]  时,right = mid -1;target > nums[mid] 时,left = mid + 1;

计算上边界时,当  target < nums[mid] 时,right = mid -1; target >= nums[mid] 时 left = mid + 1;刚好和计算下边界时条件相反,返回right。

public class Solution {public static int[] searchRange(int[] nums,int target){int upper = upperBound(nums, target);int low = lowerBound(nums, target);//不存在情况if (upper < low){return new int[]{-1,-1};}return new int[]{low,upper};}//计算下边界static int lowerBound(int[] nums,int target){int left = 0, right = nums.length - 1;while (left <= right){//计算midint mid = left + ((right - left) >> 1);if (target <= nums[mid]){//当目标值小于等于nums[mid]时,继续在左区间检索,找到第一个数right = mid -1;}else if (target > nums[mid]){//目标值大于nums[mid]时,则在右区间继续检索,找到第一个等于目标值的数left = mid + 1;}}return left;}//计算上边界static int upperBound(int[] nums,int target){int left = 0,right = nums.length - 1;while (left <= right){int mid = left + ((right - left) >> 1);if (target >= nums[mid]){left = mid + 1;}else if (target < nums[mid]){right = mid - 1;}}return right;}
}

 

相关文章:

  • Visio框图自动带填充色原因及如何取消
  • Windows系统安装MySQL8.0.38
  • Linux 程序置顶脚本
  • 深入理解pytest fixture:提升测试的灵活性和可维护性!
  • 汉光联创HGLM2200N黑白激光多功能一体机加粉及常见问题处理
  • springcloud-config服务器,同样的配置在linux环境下不生效
  • 【Qt之·类QVariant·数据类型】
  • 【Rust入门】生成随机数
  • decode()方法——解码字符串
  • tp8 mysql8原生查询统计
  • Python学生信息管理系统(完整代码)
  • PhysioLLM 个性化健康洞察:手表可穿戴设备实时数据 + 大模型
  • 代码随想录训练营第二十八天 122买卖股票的最佳时间II 55跳跃游戏 45跳跃游戏II 1005K次取反后最大化的数组和
  • 使用React复刻ThreeJS官网示例——keyframes动画
  • #数据结构 笔记三
  • 【vuex入门系列02】mutation接收单个参数和多个参数
  • Android开源项目规范总结
  • DOM的那些事
  • Java IO学习笔记一
  • JavaScript工作原理(五):深入了解WebSockets,HTTP/2和SSE,以及如何选择
  • Laravel Telescope:优雅的应用调试工具
  • SpiderData 2019年2月13日 DApp数据排行榜
  • Vue学习第二天
  • 搞机器学习要哪些技能
  • 关于extract.autodesk.io的一些说明
  • 解决iview多表头动态更改列元素发生的错误
  • 嵌入式文件系统
  • 如何借助 NoSQL 提高 JPA 应用性能
  • 微服务核心架构梳理
  • 智能合约Solidity教程-事件和日志(一)
  • 数据可视化之下发图实践
  • 通过调用文摘列表API获取文摘
  • ​​​​​​​GitLab 之 GitLab-Runner 安装,配置与问题汇总
  • ​iOS安全加固方法及实现
  • # 计算机视觉入门
  • #1014 : Trie树
  • #laravel 通过手动安装依赖PHPExcel#
  • (2024)docker-compose实战 (8)部署LAMP项目(最终版)
  • (70min)字节暑假实习二面(已挂)
  • (Matlab)基于蝙蝠算法实现电力系统经济调度
  • (Oracle)SQL优化技巧(一):分页查询
  • (二)构建dubbo分布式平台-平台功能导图
  • (二)丶RabbitMQ的六大核心
  • (二刷)代码随想录第15天|层序遍历 226.翻转二叉树 101.对称二叉树2
  • (免费领源码)python+django+mysql线上兼职平台系统83320-计算机毕业设计项目选题推荐
  • (七)微服务分布式云架构spring cloud - common-service 项目构建过程
  • (五)IO流之ByteArrayInput/OutputStream
  • (五)大数据实战——使用模板虚拟机实现hadoop集群虚拟机克隆及网络相关配置
  • (一)pytest自动化测试框架之生成测试报告(mac系统)
  • (转)IOS中获取各种文件的目录路径的方法
  • **CI中自动类加载的用法总结
  • ./和../以及/和~之间的区别
  • .chm格式文件如何阅读
  • .NETCORE 开发登录接口MFA谷歌多因子身份验证
  • .NET开发人员必知的八个网站