代码随想录—力扣算法题:704二分查找.Java版(示例代码与导图详解)

版本说明

当前版本号[20230802]。

版本修改说明
20230802初版

目录

文章目录

  • 版本说明
  • 目录
  • 数组
    • 数组理论基础
    • 二分查找
      • 思路
      • 左闭右闭[left, right]
      • 左闭右开[left, right)
      • 两种方法的区别
      • 总结

数组

数组理论基础

数组是存放在连续内存空间上的相同类型数据的集合。

数组可以方便的通过下标索引的方式获取到下标下对应的数据

举一个字符数组的例子,如图所示:

image-20230802165513000

需要两点注意的是

  • 数组下标都是从0开始的。
  • 数组内存空间的地址是连续的

正是因为数组的在内存空间的地址是连续的,所以我们在删除或者增添元素的时候,就难免要移动其他元素的地址。

例如删除从左往右数第4个、下标为3的元素,就要对下标为3的元素后面的所有元素都要做移动操作,如图所示:

image-20230802165611438

数组的元素是不能删的,只能覆盖。

那么二维数组中,第一个[]代表是行(第一索引),第二个[]代表是列(第二索引)

image-20230802165700459

上面的右图中,

b[0][1]    代表的是第0行,第1列      对于图中是数字:4
b[2][0]    代表的是第2行,第0列      对于图中是数字:4

那么二维数组在内存的空间地址是连续的么?

以Java为例,也做一个实验。

package shuzhu;public class Day01
{public static void main(String[] args) {int[][] arr = {{1, 2, 3}, {3, 4, 5}, {6, 7, 8}, {9,9,9}};System.out.println(arr[0]);System.out.println(arr[1]);System.out.println(arr[2]);System.out.println(arr[3]);}
}

所显示:

image-20230802171505113

这里的数值也是16进制,这不是真正的地址,而是经过处理过后的数值了,我们也可以看出,二维数组的每一行头结点的地址是没有规则的,更谈不上连续。

所以Java的二维数组可能是如下排列的方式:

image-20230802171547984

二分查找

力扣题目链接

给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。

示例 1:

输入: nums = [-1,0,3,5,9,12], target = 9     
输出: 4       
解释: 9 出现在 nums 中并且下标为 4     

示例 2:

输入: nums = [-1,0,3,5,9,12], target = 2     
输出: -1        
解释: 2 不存在 nums 中因此返回 -1        

提示:

  • 你可以假设 nums 中的所有元素是不重复的。
  • n 将在 [1, 10000]之间。
  • nums 的每个元素都将在 [-9999, 9999]之间。

思路

这道题目的前提是数组为有序数组,同时题目还强调数组中无重复元素,因为一旦有重复元素,使用二分查找法返回的元素下标可能不是唯一的,这些都是使用二分法的前提条件,当大家看到题目描述满足如上条件的时候,可要想一想是不是可以用二分法了。

​ 大家写二分法经常写乱,主要是因为对区间的定义没有想清楚,区间的定义就是不变量。要在二分查找的过程中,保持不变量,就是在while寻找中每一次边界的处理都要坚持根据区间的定义来操作,这就是循环不变量规则。

​ 写二分法,区间的定义一般为两种,左闭右闭即[left, right],或者左闭右开即[left, right)。

​ 下面我用这两种区间的定义分别讲解两种不同的二分写法。

左闭右闭[left, right]

区间的定义这就决定了二分法的代码应该如何写,因为定义target在[left, right]区间,所以有如下两点:

  • while (left <= right) 要使用 <= ,因为left == right是有意义的,所以使用 <=
  • if (nums[middle] > target) right 要赋值为 middle - 1,因为当前这个nums[middle]一定不是target,那么接下来要查找的左区间结束下标位置就是 middle - 1

image-20230802211631054

  • 如上图,因为当 if (nums[middle] > target) 时,我们假设middle大于target了,这个范围是在浅蓝色椭圆形区域内,而我们right值取值范围则只能小于这块区域了,只能在橙红色箭头所指左边区域内,因此right可取最大范围为 middle - 1 .

image-20230802212433027

  • 如上图,因为当 if (nums[middle] < target) 时,我们假设middle小于target了,这个范围是在浅蓝色椭圆形区域内,而我们right值取值范围则只能小于这块区域了,只能在橙红色箭头所指左边区域内,因此left可取最小范围为 middle + 1 .

例如在数组:1,2,3,4,7,9,10中查找元素2,如图所示:

image-20230802180936413

Java示例左闭右闭代码:

public class Day01 {public static int search(int[] nums, int target) {// 避免当 target 小于nums[0] nums[nums.length - 1]时多次循环运算if (target < nums[0] || target > nums[nums.length - 1]) {return -1;}int left = 0, right = nums.length - 1;while (left <= right) {int mid = left + ((right - left) >> 1);// 如果目标值在中间位置,返回下标if (nums[mid] == target)return mid;// 如果目标值比中间位置的数大,增大左边界else if (nums[mid] < target)left = mid + 1;// 如果目标值比中间位置的数小,缩小右边界else if (nums[mid] > target)right = mid - 1;}return -1;}
}

左闭右开[left, right)

有如下两点:

  • while (left < right),这里使用 < ,因为left == right在区间[left, right)是没有意义的
  • if (nums[middle] > target) right 更新为 middle,因为**当前nums[middle]不等于target,去左区间继续寻找,而寻找区间是左闭右开区间,所以right更新为middle,**即:下一个查询区间不会去比较nums[middle]

image-20230802211631054

  • 如上图,因为当 if (nums[middle] > target) 时,我们假设middle大于target了,这个范围是在浅蓝色椭圆形区域内,而我们right值取值范围则只能小于这块区域了,只能在橙红色箭头所指左边区域内,但由于我们是左闭右开,本身就取不到right,因此right可取最大范围为 middle .

​ left 依然等于 middle + 1 .

在数组:1,2,3,4,7,9,10中查找元素2,如图所示:(注意和方法一的区别

image-20230802181127017

Java示例左闭右开代码:

public class Day01 {public static int search(int[] nums, int target) {int left = 0, right = nums.length;while (left < right) {int mid = left + ((right - left) >> 1);// 如果目标值在中间位置,返回下标if (nums[mid] == target)return mid;// 如果目标值比中间位置的数大,增大左边界else if (nums[mid] < target)left = mid + 1;// 如果目标值比中间位置的数小,缩小右边界else if (nums[mid] > target)right = mid;}return -1;}
}

两种方法的区别

  1. 左闭右闭方式

    • 【同】定义范围:初始时,左边界left指向数组的第一个元素,右边界right指向数组的最后一个元素。
    • 【同】循环终止条件:当left大于right时,表示搜索范围为空,循环终止。
    • **【异】**循环体内逻辑:首先计算中间位置mid,然后判断目标值与中间位置的关系,如果目标值小于等于中间位置的值,则将搜索范围缩小到左半部分,即将右边界right更新为mid-1;如果目标值大于中间位置的值,则将搜索范围缩小到右半部分,即将左边界left更新为mid+1
    • 在左闭右闭求法中,将右边界right初始化为nums.length - 1的原因是确保初始的搜索范围包含整个数组。如果右边界初始化为nums.length,那么在初始化时就将右边界设置为超出数组范围的位置,即right = nums.length,这样**在迭代过程中会漏掉最后一个元素。**所以,为了包含整个数组,右边界right的初始值应为nums.length - 1
    • 在给定代码的search方法中,如果目标值小于数组的最小值nums[0]或大于数组的最大值nums[nums.length - 1],就直接返回-1。这样可以避免进行多余的迭代,提高效率。
  2. 左闭右开方式

    • 【同】定义范围:初始时,左边界left指向数组的第一个元素,右边界right指向数组的最后一个元素的下一个位置。

    • 【同】循环终止条件:当left等于right时,表示搜索范围为空,循环终止。

    • **【异】**循环体内逻辑:首先计算中间位置mid,然后判断目标值与中间位置的关系,如果目标值小于中间位置的值,则将搜索范围缩小到左半部分,即将右边界right更新为mid;如果目标值大于等于中间位置的值,则将搜索范围缩小到右半部分,即将左边界left更新为mid+1

    • 在二分查找题的左闭右开求法中,将右边界right初始化为nums.length的原因是确保初始的搜索范围包含整个数组。如果右边界初始化为nums.length - 1,那么在初始化时就将右边界设置为数组最后一个元素的位置,即right = nums.length - 1,这样**在迭代过程中会漏掉最后一个元素。**所以,为了包含整个数组,右边界right的初始值应为nums.length

    • 在给定代码的search方法中,循环终止条件是left < right,而不是传统的left <= right这是因为右边界right的定义是开区间,而不是闭区间。当leftright相等时,搜索范围为空,循环终止。

      两种方式的不同之处在于循环终止条件的判断和范围的定义方式,但它们都可以实现二分查找的功能。

总结

​ 其实主要就是对区间的定义没有理解清楚,在循环中没有始终坚持根据查找区间的定义来做边界处理。

​ 区间的定义就是不变量,那么在循环中坚持根据查找区间的定义来做边界处理,就是循环不变量规则。

​ 本篇根据两种常见的区间定义,给出了两种二分法的写法,每一个边界为什么这么处理,都根据区间的定义做了详细介绍。

测试代码:

package shuzhu;public class Day01 {public static int search(int[] nums, int target) {int left = 0, right = nums.length;while (left < right) {int mid = left + ((right - left) >> 1);// 如果目标值在中间位置,返回下标if (nums[mid] == target)return mid;// 如果目标值比中间位置的数大,增大左边界else if (nums[mid] < target)left = mid + 1;// 如果目标值比中间位置的数小,缩小右边界else if (nums[mid] > target)right = mid;}return -1;}public static void main(String[] args) {int[] nums = {1, 3, 5, 7, 9, 11, 13};int target = 13;int result = search(nums, target);if (result == -1) {System.out.println("目标值不在数组中。");} else {System.out.println("目标值在数组中的下标为:" + result);}}
}

更多内容可点击此处跳转到代码随想录,看原版文件

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

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

相关文章

基于Spring Boot的美食分享网站设计与实现(Java+spring boot+MySQL)

获取源码或者论文请私信博主 演示视频&#xff1a; 基于Spring Boot的美食分享网站设计与实现&#xff08;Javaspring bootMySQL&#xff09; 使用技术&#xff1a; 前端&#xff1a;html css javascript jQuery ajax thymeleaf 微信小程序 后端&#xff1a;Java springboot…

深入解析Linux进程内存:VSS、RSS、PSS、USS及查看方式

VSS 虚拟耗用内存大小&#xff0c;是进程可以访问的所有虚拟内存的总量&#xff0c;包括进程独自占用的物理内存、和其他进程共享的内存、分配但未使用的内存。 RSS 驻留内存大小&#xff0c;是进程当前实际占用的物理内存大小&#xff0c;包括进程独自占用的物理内存、和其…

1400*C. Computer Game

Example input 6 15 5 3 2 15 5 4 3 15 5 2 1 15 5 5 1 16 7 5 2 20 5 7 3 output 4 -1 5 2 0 1 解析&#xff1a; k个电&#xff0c; 第一种为 k>a 时&#xff0c;只玩游戏 k-a; 第二种&#xff0c;k>b,一边玩一边充电 k-b 问完成n轮游戏的情况下&#xff0c;优先第…

配置VS Code 使其支持vue项目断点调试

起因 每个应用&#xff0c;不论大小&#xff0c;都需要理解程序是如何运行失败的。当我们写的程序没有按照自己写的逻辑走的时候&#xff0c;我们就会逐步一一排查问题。在平常开发过程中我们可能会借助 console.log 来排查,但是现在我们可以借助 VS Code 断点来调试项目。 前…

防火墙监控工具

防火墙监控是跟踪在高效防火墙性能中起着关键作用的重要防火墙指标&#xff0c;防火墙监控通常应包括&#xff1a; 防火墙日志监控防火墙规则监控防火墙配置监控防火墙警报监控 防火墙监控服务的一个重要方面是它应该是主动的。主动识别内部和外部安全威胁有助于在早期阶段识…

使用Gunicorn+Nginx部署Flask项目

部署-开发机上的准备工作 确认项目没有bug。用pip freeze > requirements.txt将当前环境的包导出到requirements.txt文件中&#xff0c;方便部署的时候安装。将项目上传到服务器上的/srv目录下。这里以git为例。使用git比其他上传方式&#xff08;比如使用pycharm&#xff…

【JavaEE】深入了解Spring中Bean的可见范围(作用域)以及前世今生(生命周期)

【JavaEE】Spring的开发要点总结&#xff08;4&#xff09; 文章目录 【JavaEE】Spring的开发要点总结&#xff08;4&#xff09;1. Bean的作用域1.1 一个例子感受作用域的存在1.2 通过例子说明作用域的定义1.3 六种不同的作用域1.3.1 singleton单例模式&#xff08;默认作用域…

隐藏程序文档窗口工具1.0下载

在录屏或直播时有些窗口&#xff0c;比如讲稿提词器等&#xff0c;不想录进视频里&#xff0c;或者不想被观众看到&#xff0c;您可以使用这个窗口隐藏工具。 这个隐藏并不是真的隐藏了&#xff0c;我们在电脑上依然可以看到&#xff0c;但是直播或录屏工具抓取不到了&#xf…

Java版工程行业管理系统源码-专业的工程管理软件- 工程项目各模块及其功能点清单 em

&#xfeff;Java版知识付费源码 Spring CloudSpring BootMybatisuniapp前后端分离实现知识付费平台 提供职业教育、企业培训、知识付费系统搭建服务。系统功能包含&#xff1a;录播课、直播课、题库、营销、公司组织架构、员工入职培训等。 提供私有化部署&#xff0c;免费售…

android Glide加载gif动图和本地视频,Java

droid Glide加载gif动图和本地视频&#xff0c;Java //从手机存储本地加载视频 String filePath "/storage/emulated/0/Pictures/my_video.mp4"; Glide .with( context ).load( Uri.fromFile( new File( filePath ) ) ).into( imageView );//加载gif Glide .with(…

【JAVA】String ,StringBuffer 和 StringBuilder 三者有何联系?

个人主页&#xff1a;【&#x1f60a;个人主页】 系列专栏&#xff1a;【❤️初识JAVA】 文章目录 前言StringBufferStringBuffer方法 StringBuilderStringBuilder方法 String &#xff0c;StringBuffer 和 StringBuilder的区别String和StringBuffer互相转换 前言 在之前的文章…

改变C++中私有变量成员的值

1、没有引用的情况&#xff1a; #include <iostream> #include <queue> using namespace std; class Person { public:queue<int>que; public:queue<int> getQueue(){return que;}void push(int a){que.push(a);}void pop(){que.pop();} };int main()…

SpringBoot2学习笔记

信息来源&#xff1a;https://www.bilibili.com/video/BV19K4y1L7MT?p5&vd_source3969f30b089463e19db0cc5e8fe4583a 作者提供的文档&#xff1a;https://www.yuque.com/atguigu/springboot 作者提供的代码&#xff1a;https://gitee.com/leifengyang/springboot2 ----…

Verilog语法学习——边沿检测

边沿检测 代码 module edge_detection_p(input sys_clk,input sys_rst_n,input signal_in,output edge_detected );//存储上一个时钟周期的输入信号reg signal_in_prev;always (posedge sys_clk or negedge sys_rst_n) beginif(!sys_rst_n)signal_in_prev < 0;else…

12. Mybatis 多表查询 动态 SQL

目录 1. 数据库字段和 Java 对象不一致 2. 多表查询 3. 动态 SQL 使用 4. if 标签 5. trim 标签 6. where 标签 7. set 标签 8. foreach 标签 9. 通过注解实现 9.1 查找所有数据 9.2 通过 id 查找 1. 数据库字段和 Java 对象不一致 我们先来看一下数据库中的数…

解决Hadoop审计日志hdfs-audit.log过大的问题

【背景】 新搭建的Hadoop环境没怎么用&#xff0c;就一个环境天天空跑&#xff0c;结果今天运维告诉我说有一台服务器磁盘超过80%了&#xff0c;真是太奇怪了&#xff0c;平台上就跑了几个spark测试程序&#xff0c;哪来的数据呢&#xff1f; 【问题调查】 既然是磁盘写满了&…

mysql主从配置及搭建(gtid方式)

一、搭建主从-gtid方式 搭建步骤查看第一篇。bin-log方式。可以进行搭建1.1 gtid和二进制的优缺点 使用 GTID 的主从复制优点&#xff1a; 1、简化配置&#xff1a;使用 GTID 可以简化主从配置&#xff0c;不需要手动配置每个服务器的二进制日志文件和位置。 2、自动故障转移…

MySQL 其他数据库日志

我们了解数据库事务时&#xff0c;知道两种日志&#xff1a;重做日志&#xff0c;回滚日志。 对于线上数据库应用系统&#xff0c;突然遭遇 数据库宕机 怎么办&#xff1f;在这种情况下&#xff0c;定位宕机的原因 就非常关键。我们可以查看数据库的 错误日志。因为日志中记录…

给你一个小技巧,解放办公室管理!

电力的稳定供应对于现代社会中的办公室和企业来说至关重要。为了应对这些潜在的问题&#xff0c;许多办公室和企业都采用了不间断电源&#xff08;UPS&#xff09;系统来提供电力备份。UPS可以保持关键设备的运行&#xff0c;确保生产和业务不受干扰。 然而&#xff0c;仅仅安装…

力扣468 验证IP地址

ipv4地址&#xff1a;1.必须是四个非空子串 2.每个非空子串不含前导零 3.子串里字符只能是0~255 ipv6地址&#xff1a;1.必须是八个非空子串 2。每段非空串得长度是否在1~4之间&#xff0c;且不含0-9&#xff0c;a-f&#xff0c;A-F之外得字符。 3.同时0-9也不允许含前导零 cl…