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

news2024/12/26 22:34:54

版本说明

当前版本号[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.coloradmin.cn/o/827398.html

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

相关文章

Python集成开发环境IDE:Spyder自动换行、函数列表outline、代码折叠

Spyder是一个用PythonQt编写的集成开发环境&#xff0c;包含许多有用的函数和工具。以下是一些常用功能&#xff1a; 变量浏览器&#xff1a;可以动态交互并修改变量&#xff0c;可以进行绘制直方图、时间序列&#xff0c;编辑日期框架或Numpy数组&#xff0c;对集合进行排序&…

100 个鲜为人知的 Python 高级技巧 0-20

100 鲜为人知的 Python 功能 这篇文章是为那些每天使用 Python&#xff0c;但从未真正坐下来通读所有文档的人准备的。 如果您已经使用 Python 多年&#xff0c;并且知道足够多的知识来完成工作&#xff0c;那么为了发现一些新技巧而通读几千页的文档可能不是明智之举。 因此&a…

IPC进程间通信探索——管道的原理与特点

&#x1f923; 爆笑教程 &#x1f449; 《看表情包学Linux》 &#x1f525; CSDN 累计订阅量破千的火爆 C/C 教程的 2023 重制版&#xff0c;C 语言入门到实践的精品级趣味教程。了解更多&#xff1a; &#x1f449; "不太正经" 的专栏介绍 ← 试读第一章订阅链接&am…

Babel编译与Webpack

目录 Babel初识BabelBabel 使用方式使用 Babel 前的准备工作 WebpackWebpack介绍Webpack初体验Webpack核心概念入口&#xff08;entry&#xff09;出口&#xff08;output&#xff09;加载 (loader)插件&#xff08;plugins&#xff09; Babel Babel官网: https://babeljs.io/…

贝锐蒲公英:没有公网IP,多分支企业如何高效远程访问OA系统?

贝锐蒲公英&#xff1a;没有公网IP&#xff0c;多分支企业、移动办公人员如何高效远程访问OA系统&#xff1f; 国内某大型美妆公司&#xff0c;旗下产品覆盖美容护肤品、彩妆、美容仪器、健康食品、SPA美容会所及等多类服务&#xff0c;致力于为客户提供高品质的产品和完善的服…

Centos7搭建Apache Storm 集群运行环境

文章目录 1. 安装 Java2. 下载并解压 Storm3. 配置环境变量4. 配置 ZooKeeper5. 配置 Stormstorm.yaml自定义 storm.yamlstorm-env.shlogback/cluster.xml 6. 启动 Storm 集群7. 验证 1. 安装 Java Storm 运行在 Java 平台上&#xff0c;因此需要先安装 Java。你可以使用以下命…

C++ 类的友元

【例1】 将数据与处理数据的函数封装在一起&#xff0c;构成类&#xff0c;既实现了数据的共享又实现了隐藏&#xff0c;无疑是面向对象程序设计的一大优点。但是封装并不总是绝对的。现在考虑一个简单的例子&#xff0c;就是Point类&#xff0c;每一个Point类的对象代表一个“…

《零基础入门学习Python》第075讲:GUI的终极选择:Tkinter12

Tkinter 的基本组件我们已经介绍得七七八八了&#xff0c;剩下的一些我们在这节课全部都会讲解完毕。 &#xff08;一&#xff09;Message组件 Message&#xff08;消息&#xff09;组件是 Label 组件的变体&#xff0c;用于显示多行文本消息。众所周知&#xff0c;我们的Lab…

简单的Kubernetes集群二进制方式部署

Kubernetes二进制方式部署 一&#xff1a;操作系统初始化配置&#xff08;所有机子&#xff09;关闭防火墙关闭selinux关闭swap根据规划设置主机名在master添加hosts调整内核参数时间同步 二&#xff1a;部署 etcd 集群1.准备签发证书环境#准备cfssl证书生成工具生成Etcd证书编…

在SIP 语音呼叫中出现单通时要怎么解决?

在VoIP的环境中&#xff0c;特别是基于SIP通信的环境中&#xff0c;我们经常会遇到一些非常常见的问题&#xff0c;例如&#xff0c;单通&#xff0c;注册问题&#xff0c;回声&#xff0c;单通等。这些问题事实上都有非常直接的排查方式和解决办法&#xff0c;用户可以按照一定…

Quartz中集群模式源码级解析

文章目录 案例搭建 案例搭建 创建一个JOB实现类 package org.quartz.examples.example13;import org.quartz.*;import java.util.Date;/*** This job has the same functionality of SimpleRecoveryJob except that this job implements is stateful, in that it* will have …

Spring框架——IOC配置文件方式

Spring框架的概述和入门 目录 Spring框架的概述和入门 什么是Spring框架 Spring框架的特点 Spring框架的IOC核心功能快速入门 Spring框架中的工厂&#xff08;了解&#xff09; Spring 创建Bean对象的三种方式 Spring框架的Bean管理的配置文件方式 Spring框架中标签的配…

Token与Cookie、Session登录机制

Cookie 背景 Web 的兴起&#xff08;所谓交互式就是你不光可以浏览&#xff0c;还可以登录&#xff0c;发评论&#xff0c;购物等用户操作的行为&#xff09;&#xff0c;单纯地浏览 web 已经无法满足人们的要求&#xff0c;比如随着网上购物的兴起&#xff0c;需要记录用户的…

寻找丢失数字:数学与位运算的解密之旅

本篇博客会讲解力扣“268. 丢失的数字”的解题思路&#xff0c;这是题目链接。 注意进阶中的描述&#xff1a;你能否实现线性时间复杂度、仅使用额外常数空间的算法解决此问题&#xff1f;这里我会讲解两种思路&#xff0c;它们的时间复杂度是O(N)&#xff0c;空间复杂度是O(1)…

STM32F1基于标准库ST7735 1.8‘‘LCD显示DHT11数据

STM32基于标准库ST7735 1.8‘’LCD显示DHT11数据 &#x1f4cd;HAL库驱动可以参考&#xff1a;《STM32基于HAL工程读取DHT11数据》&#x1f33c;显示效果&#xff1a; &#x1f33b;ST7735 128x160 1.8’LCD屏幕 &#x1f4cc;屏幕资料和相关驱动可以参考《1.8寸TFT LCD128…

JDK各版本重要变革

各版本更新详情 JDK8(LTS)--2014/3 语法层面 lambda表达式(重要特色之一) 一种特殊的匿名内部类,语法更加简洁允许把函数作为一个方法的参数,将代码象数据一样传递&#xff0c;即将函数作为方法参数传递基本语法: <函数式接口> <变量名> (参数...) -> { 方法…

迷你主机中的战斗机 Intel NUC 12 Serpent Canyon拆解

千呼万唤始出来&#xff0c;新一代游戏和创作者性能怪兽 mini主机 NUC 12 Serpent Canyon&#xff08;巨蛇峡谷终于发售了&#xff0c;以超紧凑的 2.5 升尺寸提供用户所需的所有性能和创新功能。NUC 12 Enthusiast 还首次将 Intel Deep Link 引入桌面&#xff0c;使 CPU 和 GPU…

类的继承和super关键字的使用(JAVA)

继承 所有的OOP语言都会有三个特征&#xff1a; 封装&#xff08;点击可跳转&#xff09;&#xff1b;继承&#xff1b;多态 为什么会有继承呢&#xff1f;可以先看下面的例子&#xff1a; 上面这两个类中的代码很相似因为它们只有最后一个方法不同其它的都相同&#xff0c;这样…

DbVisualizer Pro Crack

DbVisualizer Pro Crack DbVisualizer是适用于开发人员、DBA和分析师的通用数据库工具。它是最终的解决方案&#xff0c;因为相同的工具可以在访问各种数据库的所有主要操作系统上使用。支持的数据库Amazon Redshift、DB2 LUW、Exasol、H2、Informix、JavaDB/Derby、Microsoft …

【项目 进程10】2.21 alarm函数 2.22setitimer定时器函数

2.21 alarm函数 #include <unistd.h> unsigned int alarm(unsigned int seconds);功能&#xff1a;设置定时器&#xff08;闹钟&#xff09;。函数调用&#xff0c;开始倒计时&#xff0c;当倒计时为0的时候&#xff0c; 函数会给当前的进程发送一个信号&#xff1a;SIG…