leetCode-hot100-二分查找专题

二分查找

    • 简介
    • 原理分析
    • 易错点分析
    • 例题
      • 33.搜索旋转排序数组
      • 34.在排序数组中查找元素的第一个和最后一个位置
      • 35.搜索插入位置
      • 240.搜索二维矩阵 Ⅱ

简介

二分查找,是指在有序(升序/降序)数组查找符合条件的元素,或者确定某个区间左右边界,是一种效率较高的查找方法。

原理分析

必须是有序的序列,否则二分没有效果,数据元素一般是数据型,可以比较大小。
步骤:
(1)初始化lr,确定循环条件(一般为 while(l < r)
(2)计算mid值(一般为mid = (l + r) / 2
数据元素为奇数,例如[1234567]
在这里插入图片描述
数据元素为偶数,例如[123456]
在这里插入图片描述

(3)将目标元素与查找区间的中间值作比较(当二者相等时,查找结束),更新lr的值。
(4)重复第(3)步。

易错点分析

关于mid是否要+1?
有的时候,mid需要+1(如例题34),一般如果l=mid的时候mid需要+1,否则会死循环,l=mid+1的时候mid不需要+1。
关于整数溢出问题
mid = l + (r - l) / 2这个计算方式避免了直接使用(l+ r) / 2可能导致的整数溢出问题。
在二分查找法中,mid = l1 + (r1 - l1) / 2是为了计算当前搜索区间的中间位置。这个计算方式避免了直接使用(l1 + r1) / 2可能导致的整数溢出问题。
解释如下:

  1. lr都很大时,l + r可能会超过整数的最大值,从而导致溢出。
  2. 使用l + (r - l) / 2,首先计算了r - l,这个结果一定小于或等于rl中的较大值,因此不会导致溢出。
  3. 然后将这个差值除以2,得到中间位置与l的偏移量。
  4. 最后将偏移量加到l上,得到中间位置的索引mid
    这种方法确保了即使在lr非常大时,也能正确计算中间位置的索引,而不会因为整数溢出而导致计算错误,这是二分查找中常用的一种技巧,以确保算法的鲁棒性。

关于边界判断,是l < r还是l <= r?以及对应情况下的l和r的更新
在二分查找中,while (l <= r)while (l < r)的选择取决于搜索区间的定义。两种情况下的lr更新方式略有不同,以适应不同的区间定义。

  1. 当使用while (l <= r)时,搜索区间是左闭右闭的,即包含lr。在这种情况下:
    • l的初始值通常是0,表示搜索的起始位置。
    • r的初始值通常是数组的长度减1,表示搜索的结束位置。
    • 在循环中,如果中间元素不等于目标值,需要更新lr来缩小搜索区间。如果中间元素小于目标值,更新l = mid + 1;如果中间元素大于目标值,更新r = mid - 1
    • 循环继续直到l超过r,此时搜索结束。
  2. 当使用while (l < r)时,搜索区间是左闭右开的,即包含l但不包含r。在这种情况下:
    • l的初始值通常是0,表示搜索的起始位置。
    • r的初始值通常是数组的长度,表示搜索的结束位置之后的位置。
    • 在循环中,如果中间元素不等于目标值,同样需要更新lr。如果中间元素小于目标值,更新l = mid + 1;如果中间元素大于目标值,更新r = mid,因为mid位置的元素已经检查过,不需要再次检查。
    • 循环继续直到l等于r,此时搜索结束。

在两种情况下,计算中间位置mid的方式通常是相同的,即mid = l + (r - l) / 2。这样可以避免直接相加lr可能导致的整数溢出问题。
总结来说,while (l <= r)适用于左闭右闭的搜索区间,而while (l < r)适用于左闭右开的搜索区间。在更新lr时,需要根据区间的定义来决定是否包含中间位置mid
但是需要注意的是,这只是二分查找的基础用法,很多题目都需要根据问题进行具体的分析。

例题

33.搜索旋转排序数组

思路
本题采用两次二分查找,第一次查找找到旋转点,从而将数组分为两个部分(因为数组是升序,所以只要比较中间点元素的值和nums[0]的值,如果大于第一个元素的值,说明旋转点在中间点的右侧,更新l值,如果小于,说明旋转点在中间点的左侧,更新r值),这两个部分都是升序的,然后通过确定target和第一个元素的值的大小关系可以确定target在哪一个部分,并更新l或者r的值,再通过二分查找来确定target的下标。视频讲解点击视频讲解-搜索旋转排序数组。
时间复杂度
时间复杂度为O(logn),使用了两次二分查找。
代码实现

class Solution {public int search(int[] nums, int target) {//二分法查找旋转点int l = 0;int r = nums.length - 1;while(l < r){int mid = l + (r - l) / 2 + 1;if(nums[mid] >= nums[0]) l = mid;else r = mid - 1;}//确定target的区间if(target >= nums[0]) l = 0;else{l = l + 1;r = nums.length - 1;} //二分法查找target的下标while(l < r){int mid = l + (r - l) / 2 + 1;if(nums[mid] <= target) l = mid;else r = mid -1;}//这里用r是因为前面有l+1,使用l可能会越界return nums[r] == target ? r : -1;}
}

注意:二分查找有很多变体,l和r更新的方式不同循环条件也会不同,根据易错点分析中边界判断的规律本题也可以写成下面的形式:

class Solution {public int search(int[] nums, int target) {int l = 0;int r = nums.length - 1;//当循环条件变为l <= r时,l的更新方式为l = mid + 1//同时mid在计算时不需要+1while(l <= r){int mid = l + (r - l) / 2;if(nums[mid] >= nums[0]) l = mid + 1;else r = mid - 1;}if(target >= nums[0]){l = 0;}else {l = l + 1;r = nums.length - 1;}//当循环条件变为l <= r时,l的更新方式为l = mid + 1//同时mid在计算时不需要+1while(l <= r){int mid = l + (r - l) / 2;if(nums[mid] <= target) l = mid + 1;else r = mid - 1;}return nums[r] == target ? r : -1;}
}

34.在排序数组中查找元素的第一个和最后一个位置

思路
本题的解题思路和33题目一样,通过两次二分查找来确定左右边界,左边界的查找方法是左边界左边的元素全部小于等于左边界,右边的元素大于等于左边界;有边界的查找方法是右边界左边的元素全部小于等于右边界,右边的元素大于等于右边界,也就是两次二分即可,最后将结果放入List中,视频讲解点击视频讲解-在排序数组中查找元素的第一个和最后一个位置。
时间复杂度
时间复杂度是 O(logn),因为它使用了二分查找的方式来确定左右边界。在最坏的情况下,它需要进行两次二分查找,所以时间复杂度是 O(logn)
代码实现

class Solution {public int[] searchRange(int[] nums, int target) {//边界判断if(nums.length == 0) return new int[] {-1,-1};List<Integer> ans = new ArrayList<>(2);//确定左边界int l1 = 0;int r1 = nums.length - 1;while(l1 < r1){int mid1 = l1 + (r1 - l1) / 2;if(nums[mid1] >= target) r1 = mid1;else l1 = mid1 + 1;}if(nums[l1] != target) return new int[] {-1,-1};ans.add(l1);//确定右边界int l2 = 0;int r2 = nums.length - 1;while(l2 < r2){int mid2 = l2 + (r2 - l2) / 2 + 1;if(nums[mid2] <= target) l2 = mid2;else r2 = mid2 - 1;}ans.add(r2);//将List转为int[]int[] result = new int[ans.size()];for (int i = 0; i < ans.size(); i++) {result[i] = ans.get(i);}return result;}
}

其中,最后将List转换为数组除了上面的方法外,还可以用以下的方法:

return ans.stream().mapToInt(Integer::intValue).toArray();

这行代码使用了Java 8的流API来将List<Integer>转换为int[]数组。stream()方法创建一个流,mapToInt(Integer::intValue)将流中的Integer对象映射成int值,最后toArray()方法将流中的元素收集到一个int[]数组中。

分析
该题中的二分查找确实与常规的二分查找算法有所不同,特别是在确定左右边界的时候。这种差异主要是由于目标问题的特殊性:找到数组中等于给定值的元素的第一个和最后一个位置,这种情况下,我们需要对标准的二分查找进行一些调整。
让我们逐步分析代码中的不同之处:

  1. 确定左边界
    • while(l1 < r1):这里使用<而不是<=,是因为我们希望在找到目标值时继续向左搜索,以找到它的第一个出现的位置。如果使用<=,我们可能会在找到目标值后立即停止,这可能会导致我们得到的是目标值的最后一个位置。
    • if(nums[mid1] >= target) r1 = mid1;:如果中间元素大于或等于目标值,我们将右边界移动到中间位置。这是因为我们要找的是左边界,所以我们需要继续在左侧搜索。
  2. 确定右边界
    • while(l2 < r2):同样,这里使用<而不是<=,是因为我们希望在找到目标值时继续向右搜索,以找到它的最后一个出现的位置。
    • int mid2 = l2 + (r2 - l2) / 2 + 1;:这里对中间位置的索引进行了调整,使其向上取整。这是因为在找到目标值时,我们希望继续在右侧搜索,而上取整的中间位置将帮助我们这样做。
    • if(nums[mid2] <= target) l2 = mid2;:如果中间元素小于或等于目标值,我们将左边界移动到中间位置。这是因为我们要找的是右边界,所以我们需要继续在右侧搜索。

这种调整使得我们能够在找到目标值后继续搜索,直到我们找到它的第一个和最后一个位置。在确定了左右边界之后,我们就可以返回目标值的范围了。

35.搜索插入位置

思路:
本题是二分查找比较基础的题目,查找target在数组中的位置,需要注意的是,如果数组中没有target,那么需要返回它的插入位置,所以需要分三种情况来判断返回ll - 1l + 1,需要注意的是当target < nums[l]的时候,直接返回l - 1可能会引起数组越界,需要判断l - 1是否小于0,若小于0,则直接插入到数组首尾,即下标为0的位置,视频讲解点击视频讲解-搜索插入位置。
时间复杂度:
时间复杂度为O(logn)
代码实现:

class Solution {public int searchInsert(int[] nums, int target) {int l = 0;int r = nums.length - 1;while(l < r){int mid = l + (r - l) / 2 + 1;if(nums[mid] <= target) l = mid;else r = mid - 1;}if(nums[l] == target) return l;else if(nums[l] < target) return l + 1;else return l - 1 >= 0 ? l - 1 : 0;}
}

240.搜索二维矩阵 Ⅱ

思路:
从矩阵的右上角开始,比较当前元素与目标值的大小,根据比较结果决定向左走还是向下走,如果目标值小于当前元素则向左走,否则向下走,直到找到目标值或走到矩阵的边界。
时间复杂度:
时间复杂度为O(m+n),其中m为矩阵的行数,n为矩阵的列数,在最坏的情况下,算法会遍历整个矩阵的一行或一列。
代码实现:

class Solution {public boolean searchMatrix(int[][] matrix, int target) {int i = 0;int j = matrix[0].length - 1;while(i < matrix.length && j >= 0){//大于target,向左走;小于target,向右走if(matrix[i][j] > target){j--;}else if(matrix[i][j] < target){i++;}else{return true;}}return false;}
}

注意:
二分查找有很多变体,但是思想是不变的,比如易错点分析中总结了两种边界判断和对应lr的更新,实际上可以比较常用的还有以下这种情况:

while(l < r){int mid = l + (r - l) + 1;if(...) l = mid;else r = mid - 1;
}

此时循环结束后lr指向同一个元素,方便后续操作,所以具体问题具体分析,可以自己举个例子走一遍流程,可以更加清楚的掌握某些边界条件应该如何去写。

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

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

相关文章

找不到steam_api64.dll,无法继续执行的原因及解决方法

电脑已经成为我们生活中不可或缺的一部分。然而&#xff0c;在使用电脑的过程中&#xff0c;我们经常会遇到一些常见的问题&#xff0c;其中之一就是找不到某个特定的动态链接库文件&#xff0c;比如steamapi64.dll。这个问题可能会导致某些应用程序无法正常运行&#xff0c;给…

关于博图17安装体验过程—博图17安装失败原因(STEP7 许可证找不到)

目录 一、序言 二、正片 一、序言 该失败原因是在我使用Win11专业版安装博图17时出现的问题&#xff0c;也仅代表我的体验过程&#xff01;以下我将安装过程和解决问题的过程描述一下&#xff0c;希望可以帮助和我一样自己安装博图时能够解决出现的问题。 二、正片 如果阁下…

linux驱动学习(五)之字符设备

需要板子一起学习的可以这里购买&#xff08;含资料&#xff09;&#xff1a;点击跳转 一、 linux设备驱动分类 1、字符设备---char 应用程序与驱动程序在进行数据传输时&#xff0c;数据以"字节"为单位。 特点&#xff1a; [1] 按照顺序进行数据传输 [2] 数据传…

vscode专区

1.展示多行的文件导航标签,而非只有1行 1.1打开设置 1.2搜索该设置"workbench.editor.wrap.tabs",并勾选 1.3效果对比

vue+vscode 快速搭建运行调试环境与发布

1.安装node.js Node.js — Run JavaScript Everywhere 默认不断next 2.更换镜像地址 运行-cmd 执行以下代码安装 npm config set registry https://registry.npmmirror.com 检查node.js和镜像是否是否成功 node -v npm -v npm config get registry 3.安装打包工具 …

吊车报警的工作原理和使用场景_鼎跃安全

在现代建筑施工过程中&#xff0c;经常使用大型机械设备&#xff0c;如挖掘机、吊车、打桩机等&#xff0c;这些设备在施工过程中发挥着越来越重要的作用&#xff1b;同时&#xff0c;这些设备的作业频繁进行作业&#xff0c;对于接触到高压电线的风险也随之增加。大型机械设备…

Leetcode学习

回文数 反转一半数字 第一个想法是将数字转换为字符串&#xff0c;并检查字符串是否为回文。 但是&#xff0c;这需要额外的非常量空间来创建问题描述中所不允许的字符串。 第二个想法是将数字本身反转&#xff0c;然后将反转的数字与原始数字比较&#xff0c;如果它们是相同…

【计算机毕设】基于SpringBoot的中小企业设备管理系统设计与实现 - 源码免费(私信领取)

免费领取源码 &#xff5c; 项目完整可运行 &#xff5c; v&#xff1a;chengn7890 诚招源码校园代理&#xff01; 1. 研究目的 在中小企业中&#xff0c;设备管理是确保生产和运营效率的重要环节。传统的设备管理通常依赖于手工记录和人工管理&#xff0c;容易导致数据不准确、…

近屿OJAC带你解读:什么是ML?

概念定义 ML是机器学习&#xff08;Machine Learning&#xff09;的缩写。机器学习是人工智能的一个分支&#xff0c;它使计算机系统能够从数据中学习和改进&#xff0c;而无需进行明确的编程指令。简单来说&#xff0c;机器学习涉及到开发算法和统计模型&#xff0c;让计算机…

UE4 使用自带的插件制作音频可视化

1.插件默认为开启 2.新建共感NRT&#xff0c;选择要使用的音频 3.添加音频组件&#xff0c;添加共感NRT变量&#xff0c;选择新建的共感NRT对象 4.编写蓝图

基础—SQL—DQL(数据查询语言)分页查询

一、引言 上一篇博客学习了排序查询&#xff0c;这次来讲查询的最后一个部分&#xff1a;分页查询。 涉及到的关键字是&#xff1a;LIMIT 。 二、DQL—分页查询 对于分页&#xff0c;不管以后做的是传统的管理系统还是做互联网的项目&#xff0c;基本上都会遇到分页查询的操…

计网ppt标黄知识点整理第(4)章节——谢希仁版本、期末复习自用

路由器&#xff1a;查找转发表&#xff0c;转发分组。 IP网的意义&#xff1a;当互联网上的主机进行通信时&#xff0c;就好像在一个网络上通信一样&#xff0c;看不见互连的各具体的网络异构细节。如果在这种覆盖全球的 IP 网的上层使用 TCP 协议&#xff0c;那么就…

每天坚持写java锻炼能力---第一天(6.4)

今天的目标是菜单&#xff1a; B站/马士兵的项目菜单 package java1;import java.util.Scanner;public class Test {public static void main(String[] args) {while(true){ //3.加入死循环&#xff0c;让输入一直有System.out.println();System.out.println("--->项…

Linux 系统怎么快速「批量重命名」文件

如果需要对文件批量重命名&#xff0c;怎么办&#xff0c;是不是要找个工具&#xff0c;下载看这么使用。其实在 Linux、macOS 系统上使用脚本可以轻松搞定。 如&#xff0c;这里有一批图片文件&#xff0c;后缀名可能是jpg、jpeg、png 等&#xff0c;名称如 “我是待重命名的…

【WRF调试运行第一期】安装WRF模型所需平台

WRF实践实操第一期&#xff1a;安装WRF模型所需平台 1 操作系统2 先决条件软件3 程序流&#xff08;Program Flow&#xff09;4 文件说明软件安装1-Cygwin参考 安装 WRF&#xff08;Weather Research and Forecasting&#xff09;模型需要准备适当的硬件和软件平台。 相关介绍可…

【linux根分区扩容】

前言&#xff1a; 今天在安装软件的时候发现我的linux的根分区空间不足了&#xff0c;在网上搜索哈资料解决了。 解决根分区空间不足的问题方法&#xff1a; 第一&#xff1a;用lsblk命令查看 发现还有一些空间不在了。 第二&#xff1a;安装扩容工具&#xff1a; yum inst…

springCloud中将redis共用到common模块

一、 springCloud作为公共模块搭建框架 springCloud 微服务模块中将redis作为公共模块进行的搭建结构图&#xff0c;如下&#xff1a; 二、redis 公共模块的搭建框架 如上架构&#xff0c;代码如下pom.xml 关键代码&#xff1a; <dependencies><!-- SpringBoot Boo…

Thread Local六连问,你扛得住吗?

一、Thread Local 是什么? 线程本地变量。当使用ThreadLocal维护变量时&#xff0c;ThreadLocal为每个使用该变量的线程提供独立的变量副本&#xff0c;所以每个线程都可以独立地改变自己的副本&#xff0c;而不影响其他线程&#xff0c;做到了线程隔离。 二、Thread Local …

windows hash简介

一、hash简介 1、Windows系统使用两种方法对用户的密码进行哈希处理。它们分别是LAN Manager(LM)哈希和 NT LAN Manager(NTLM)哈希 2、所谓哈希(hash)&#xff0c;就是使用一种加密函数进行计算后的结果。这个加密函数对一个任意长度的 字符串数据进行一次数学加密函数运算…

电厂三维人员定位系统的应用与优势有哪些?

在电力行业的快速发展中&#xff0c;电厂的安全生产和管理显得尤为重要。近年来&#xff0c;随着信息技术的不断进步&#xff0c;电厂三维人员定位系统逐渐成为电厂安全管理的新利器。该系统利用三维技术&#xff0c;实现对电厂内部人员位置的实时监控与定位&#xff0c;大大提…