【算法篇】二分查找算法:基础篇
题目链接:
34.在排序数组中查找元素的第一个和最后一个位置
题目描述:
给你一个按照非递减顺序排列的整数数组 nums
,和一个目标值 target
。请你找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target
,返回 [-1, -1]
。
你必须设计并实现时间复杂度为 O(log n)
的算法解决此问题。
示例 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]
提示:
0 <= nums.length <= 105
-109 <= nums[i] <= 109
nums
是一个非递减数组-109 <= target <= 109
当left=right的时候,就是最终结果,无需判断
当我们在题目中发现这个数组有二段性的时候,就可以使用二分查找算法了
我们不能同时查找开始位置和结束位置,所以我们就先开始去查找第一个位置;
也就是开始位置:
第一步:查找区间的左端点:
根据目标值target把整个数组划分为两个部分:
把数组分为两个部分,
- 左边全部都是小于t
- 右边全部都是大于等于t
定义一个left下标指向数组的左端点,定义一个right下标指向数字的右端点,也就是指向数组的最后一个位置
接着定义一个mid中间值,把这个中间值设置为x,
分情况讨论:
1: x < t:
当这个中间值小于t的时候,落于左边的区间中,也就是没有t的区间中,此时就需要让left向右移动了,让left = mid + 1,去新的区间【left,right】,中寻找新的结果:
2: x >= t:
此时把大于和等于合在一起,不要分情况讨论了,注意,我们目前要寻找的是左边的端点,所以当中间值mid >= t的时候,就让right向左移动去寻找左端点,但是不是让rihgt = mid - 1了,万一这个mid 刚好是左端点,此时就应该让right = mid即可,更新完毕之后,就会去新的区间【left,right】中寻找了
难点是接下来的细节处理:
第一个细节:
循环的条件:我们什么时候去执行这个循环
这个循环条件有两个可以选择:
- left <= right
- left < right
此时我们只能选择第二种循环条件,因为第一种会陷入死循环:
这个循环条件中,我们不能让left = right
我们分情况来讨论:
第一种情况:
在这个【left,right】区间中含有这个taget:
第二种情况:
在这个【left,right】区间中,全部都是大于target的:
第三种情况:
这个【left,right】区间中所有的值全部都小于target:
上面我们分了三种情况去讲解为什么不需要在left = right的时候去再次执行这个循环,
下面去讲解一下为什么不可以在循环条件中加上left = right
因为一旦加上了这个left =right,就会导致死循环
在刚刚的第一种情况中会陷入死循环:
所以这就是为什么在循环条件中不可以加入left = right的原因
第二个细节:
求中点的操作:
这个中点mid应该如何去求出来:
之前我们去计算中点mid有两种方式:
- mid = left + (right - left) / 2
- mid = left + (right - left +1) / 2
第一种求中点的位置是求的靠左的位置
第二种求中点的位置是求的是靠右的位置
当使用第二种情况去求出右端点的时候会导致死循环:
当最终的时候,只剩下两个元素的时候:
所以我们求中点不可以使用第二种方法,只能去使用第一种方法了
所以以上就是求出左端点的全部过程了
第二步:查找区间的右端点
继续根据目标值把数据划分为两个部分:
- 左边的部分:小于等于target : 移动左指针,left = mid
- 右边的部分:大于target:移动右指针:right = mid - 1
之前讲述过,求中点的计算方式一共有两种:
- mid = left + (right - left) / 2
- mid = left + (right - left +1) / 2
但是中点mid的计算方式不同:
![[Pasted image 20250514160645.png]]
所以在查找区间的右端点的时候,计算中点的方式就是:
mid = left + (right - left +1) / 2
下面就是这道题目的具体代码了:
class Solution {public int[] searchRange(int[] nums, int target) {int[] ret = new int[2];ret[0] = ret[1] = -1;if(nums.length == 0){return ret;}int left = 0,right = nums.length-1;while(left <right){int mid = left+(right - left) / 2;if(nums[mid] < target){left = mid+1;}else{right = mid;}}if(nums[left] != target){return ret;}else{ret[0] = left;}left = 0;right = nums.length-1;while(left <right){int mid = left+(right - left+1) /2;if(nums[mid] <= target){left = mid;}else{right = mid-1;}}if(nums[left] != target){return ret;}else{ret[1] = right;}return ret;}
}
下面去按照8步归纳法:
第一步:用自己的话,描述一下眼前的这个问题,它是一个怎么样的问题?
给定一个数组,和目标值,在这个数组中找出目标值的开始下标和结束下标。
第二步:这个问题有哪些特征,能让我们去判断,它属于这一类的问题?
这个数组是可以划分为两段的,分开来看,先去找左端点,就会将这个数组划分为小于target的区间和大于 等于target的区间,就可以根据这个二段性去使用二分查找方法解决问题了
第三步:想要解决这类问题,切入点啥,即第一步我们要从哪里开始?
切入点,先去查找左端点,方法:根据这个target将数组划分为两段,第一段是小于target的区间,第二段是大于等于目标值的区间,然后使用二分找出这个左端点
第四步:解决这个问题的流程是怎么样的?这里说的流程是指,这道题可以分成12345…步,只要按照12345这个顺序做下去,我们就能解决这个问题。
- 先去找左端点,根据target将数组分为小于target的区间和大于等于target的区间,使用二分查找方法找出左端点,定义出左右下标,如果这个mid的值是小于target的,那就是位于小于target的区间,此时就让left = mid+1 、, 如果这个mid的值是大于等于target的,那么这个mid的值是位于大于target的区间的,那就让right = mid 即可
- 当left == right的时候,就说明此时这个left == right值就是目标值,这个left/right就是左端点了
- 根据第一步,即可求出左端点,如果没有左端点,那就返回-1
- 下面来求出右端点:方法一样,定义出左右下标,使用二分,根据这个target将这个数组划分为两个区间,一个是小于等于target的区间,一个是大于target的区间,使用二分查找方法,如果mid的值 <= target,那就是位于<=target的区间,那就让left = mid ,如果mid的值>target,那就让right =mid -1
- 最后遍历下来,当left= right的时候,循环结束,此时left = right的位置就是右端点了
- 最后使用一个两个长度的数组将这个左端点和右端点的下标放进去返回即可
第五步:在流程的12345步中,每一步的目标是什么(就是要求到些什么)?每一步需要用到哪些知识
- 当数组可以被划分为二段的时候,就可以使用二分查找方法
- 如果左区间是 <target,右区间是>= target的话,那么此时如果mid的值是位于左区间的,就left = mid+1,如果mid的值是位于右区间的,那就right = mid
- 此时这个中点的求法:mid = left + (right- left) / 2
- 当去求出右端点的时候,区间划分是< target的区间和 >= target的区间,此时这个中点的求法:mid = left + (right - left +1) /2 ;
- 如果最后没有找到,那就直接返回一个只有两个元素的数组即可
- 这个结果数组初始化为两个元素,里面是两个-1,如果一开始这个参数中的目标数组中就是一个元素都没有,那就直接返回这个只有两个-1的结果数组
第六步:要思考在运用这些知识和技巧的时候,有些需要注意的地方。
错误的区间划分方式
第一步:求出左端点的过程中,一定是要先划分出小于target区间和大于等于target区间的,不可以反回来,也就是在求左端点的时候一定不可以划分出小于等于target的区间和大于target的区间,这样是错误的方法,因为如果在求左端点的时候,按照这个错误的方法划分出了区间,会使得:
原因如下:
这种划分区间方式会导致左端点查找逻辑失效。主要问题在于当nums[mid] == target
时,你将 mid 赋值给 left,这可能会跳过真正的左端点。
在找左端点的过程中,按照这种错误的思路:
假设我们有数组[5,7,7,8,8,10]
,目标值target=8
。正确的左端点索引是 3。
- 当
nums[mid] <= target
时,令left = mid
- 当
nums[mid] > target
时,令right = mid-1
在第一次二分查找时:
- 初始区间
[0,5]
,mid=2,nums[2]=7
,满足nums[mid] <= target
,于是left=2
- 新区间
[2,5]
,mid=3,nums[3]=8
,满足nums[mid] <= target
,于是left=3
- 新区间
[3,5]
,mid=4,nums[4]=8
,满足nums[mid] <= target
,于是left=4
- 新区间
[4,5]
,mid=4,nums[4]=8
,满足nums[mid] <= target
,于是left=4
- 此后陷入死循环,无法找到左端点 3
区间划分方式无法保证左端点被包含在搜索区间内。当nums[mid] == target
时,真正的左端点可能在 mid 的左侧,但你的代码却将搜索范围向右移动,导致左端点被跳过。
第七步:要解决这个问题,最终的目标是啥?也就是说,我最终要求出的是啥?
最终的目标是求出目标数组中的含有目标值的数组下标
第八步:重新对上面的第一步至第七步,进行回顾和揣摩(包括问题类型,特征,切入点,解决过程,1234567…步,每步需要用的哪些知识方法)
(2)AI+8步归纳结合
在模仿完题目后,先自己用8步归纳法对错题进行归纳,然后让deepseek,按照8步归纳法的原则,对题目进行总结归纳,你再把自己总结归纳出的东西,跟AI对比,看看有哪些地方可以改进。
相关文章:

【算法篇】二分查找算法:基础篇
题目链接: 34.在排序数组中查找元素的第一个和最后一个位置 题目描述: 给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。 如果数组中不存在目标值 target,返…...
Qtc++开发遇到的问题-按钮点击不管用?
我在设计自己的控件的时候,遇到了按钮点击不管用的问题,而且是有的自定义控件不管用,有的管用,有的一开始管用,多点几次就不管用了, 它是这样的,一个lineEdit和位于两侧的按钮,分别…...

重磅发布 | 复旦533页《大规模语言模型:从理论到实践(第2版)》(免费下载)
在人工智能浪潮席卷全球的今天,大语言模型正以前所未有的速度推动着科技进步和产业变革。从 ChatGPT 到各类行业应用,LLM 不仅重塑了人机交互的方式,更成为推动学术研究与产业创新的关键技术。 面对这一飞速演进的技术体系,如何系…...

智能体赋能效率,企业知识库沉淀价值:UMI企业智脑的双轮驱动!
智能体企业知识库:UMI企业智脑的核心功能与价值 在人工智能技术飞速发展的今天,企业智能化转型已经成为不可逆转的趋势。作为企业级AI智能体开发平台的佼佼者,优秘智能推出的UMI企业智脑,以其强大的智能体开发能力和全面的企业知…...
STM32CubeMX,arm-none-eabi-gcc简单试用
在windows下,为stm32系列单片机编程,keil有了免费的试用版,有很多开发板示例,给学习单片机编程带来很大的方便。 STM32CubeMX提供了stm32单片机的功能设置,在输出方式上给出了几种方式,有mdk(k…...
Spring AI(一)
Spring AI 官网 Spring AI 是一个用于 AI 工程的应用程序框架。其目标是将 Spring 生态系统设计原则(如可移植性和模块化设计)应用于 AI 领域,并将使用 POJO 作为应用程序的构建块推广到 AI 领域。 Spring AI 的核心是解决了 AI 集成的根本挑战:将您的企业数据和 API 与 A…...
Nacos适配GaussDB超详细部署流程
1部署openGauss 官方文档下载 https://support.huaweicloud.com/download_gaussdb/index.html 社区地址 安装包下载 本文主要是以部署轻量级为主要教程 1.1系统环境准备 操作系统选择 系统AARCH64X86-64openEuler√√CentOS7√Docker√√1.2软硬件安装环境 版本轻量版(单…...
vue-pure-admin动态路由无Layout实现解决方案
背景: 最近在使用vue-pure-admin开发后台项目的时候发现作者并没有动态路由的全屏无Layout实现方案。查询作者路由发现,作者只做了静态路由的无Layout方案,其它动态路由,作者在做整合的时候,都放进了 \ 下面的子路由&…...

vue项目 build时@vue-office/docx报错
我在打包vue项目时, 开始用的npm run build和cnpm run build,总是提示 vue-office/docx 错误,尝试过用cnpm重新安装node_modules几次都没用。类似下面的提示一直有。 Error: [commonjs--resolver] Failed to resolve entry for package "…...
卓力达蚀刻工艺:精密制造的跨行业赋能者
引言 蚀刻技术作为现代精密制造的核心工艺之一,通过化学或物理方法对金属材料进行选择性去除,实现微米级复杂结构的加工。南通卓力达凭借20余年技术积淀与全产业链布局,成为全球高端制造领域的重要支撑力量。本文将从蚀刻技术的多领域应用与…...
【大模型面试每日一题】Day 30:解释一下 FlashAttention 技术,并对比其与传统注意力在显存效率和计算性能上的差异。
【大模型面试每日一题】Day 30:解释一下 FlashAttention 技术,并对比其与传统注意力在显存效率和计算性能上的差异。 📌 题目重现 🌟🌟 面试官:解释一下 FlashAttention 技术,并对比其与传统注…...

#RabbitMQ# 消息队列入门
目录 一 MQ技术选型 1 运行rabbitmq 2 基本介绍 3 快速入门 1 交换机负责路由消息给队列 2 数据隔离 二 Java客户端 1 快速入门 2 WorkQueue 3 FanOut交换机 4 Direct交换机 5 Topic交换机 *6 声明队列交换机 1 在配置类当中声明 2 使用注解的方式指定 7 消息转…...
在promise中,多个then如何传值
在 JavaScript 中,Promise 的多个 .then() 是链式调用的,值可以通过返回值的方式,在多个 .then() 之间传递。这是 Promise 链式调用的核心机制。 基本原理:每个 then 接收上一个 then 的返回值 new Promise((resolve, reject) &g…...
TCP 三次握手过程详解
TCP 三次握手过程详解 一、TCP握手基础概念 1.1 什么是TCP握手 TCP三次握手是传输控制协议(Transmission Control Protocol)在建立连接时的标准过程,目的是确保通信双方具备可靠的双向通信能力。 关键结论:三次握手的本质是通过序列号同步和能力协商建立可靠的逻辑连接。 …...

EPT(Efficient Prompt Tuning)方法,旨在解决提示调优(Prompt Tuning)中效率与准确性平衡和跨任务一致性的问题
EPT(Efficient Prompt Tuning)方法,旨在解决提示调优(Prompt Tuning)中效率与准确性平衡和跨任务一致性的问题 一、核心原理:分解提示与多空间投影 1. 提示分解:用低秩矩阵压缩长提示 传统问题: 长提示(如100个token)精度高但训练慢,短提示(如20个token)速度快但…...

云原生安全核心:云安全责任共担模型(Shared Responsibility Model)详解
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 1. 基础概念 什么是云安全责任共担模型? 云安全责任共担模型(Shared Responsibility Model, SRM)是云服务提供商&…...

go并发与锁之sync.Mutex入门
sync.Mutex 原理:一个共享的变量,哪个线程握到了,哪个线程可以执行代码 功能:一个性能不错的悲观锁,使用方式和Java的ReentrantLock很像,就是手动Lock,手动UnLock。 使用例子: v…...

[Java恶补day8] 3. 无重复字符的最长子串
给定一个字符串 s ,请你找出其中不含有重复字符的 最长 子串 的长度。 示例 1: 输入: s “abcabcbb” 输出: 3 解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。 示例 2: 输入: s “bbbbb” 输出: 1 解释: 因为无重复字符的最长子串是 “…...

LabVIEW教学用开发平台
一、培训目标 基础编程:掌握 LabVIEW 数据类型、程序结构、子 VI 设计与调试技巧。 硬件通信:精通 RS-232/485、TCP/IP、Modbus、PLC 等工业通信协议及实现。 高级设计模式:熟练运用状态机、生产者 - 消费者模式构建复杂测控系统。 项目实…...

Package Size Comparison – 6 Leads
Package Size Comparison 6 LeadsTSOP SOT SM SMT SOT23 SC-74 SC-59 SC-88 SOT363 US6 UMT6 SC-70 SOT563 ES EMT SC-75-6...

python打卡day38
Dataset和DataLoader 知识点回顾: Dataset类的__getitem__和__len__方法(本质是python的特殊方法)Dataloader类minist手写数据集的了解 作业:了解下cifar数据集,尝试获取其中一张图片 在遇到大规模数据集时,…...

vLLM 核心技术 PagedAttention 原理详解
本文是 vLLM 系列文章的第二篇,介绍 vLLM 核心技术 PagedAttention 的设计理念与实现机制。 vLLM PagedAttention 论文精读视频可以在这里观看:https://www.bilibili.com/video/BV1GWjjzfE1b 往期文章: vLLM 快速部署指南 1 引言…...
rpm安装jenkins-2.452
rpm安装jenkins-2.452 一、下载和安装 1、Jenkins下载 版本2.452可用windows下载: https://mirrors.jenkins-ci.org/redhat-stable/jenkins-2.452.4-1.1.noarch.rpm 其他版本 wget https://pkg.jenkins.io/redhat-stable/jenkins-2.440.3-1.1.noarch.rpm 2、jenkins安装 $r…...

《软件工程》第 2 章 -UML 与 RUP 统一过程
在软件工程领域,UML(统一建模语言)与 RUP(统一过程)是进行面向对象软件开发的重要工具和方法。接下来,我们将深入探讨第 2 章的内容,通过案例和代码,帮助大家理解和掌握相关知识。 …...

(转)Docker与K8S的区别
1 定义角度 Docker是一种开放源码的应用容器引擎,允许开发人员将其应用和依赖包打包成可移植的容器/镜像中;然后,发布到任何流行的 Linux 或 Windows 机器上,也能实现虚拟化。该容器完全使用沙箱机制,彼此之间没有任何…...
服务器数据迁移
写在前面:为满足业务需求,我们采购了一台新的高性能服务器,现在想把旧服务器中的用户文件以及conda环境等迁移到新服务器中去。为了保证迁移过程尽可能不出错,并且迁移后新的服务器可以直接使用,以下方案提供一个稳健、…...
VB.NET与SQL连接问题解决方案
1.基本连接步骤 使用SqlConnection、SqlCommand和SqlDataReader进行基础操作: vb.net Imports System.Data.SqlClient Public Sub ConnectToDatabase() Dim connectionString As String "ServermyServerAddress;DatabasemyDataBase;Integrated Security…...

商用密码 vs 普通密码:安全加密的核心区别
商用密码 vs 普通密码:安全加密的核心区别 一. 引言:密码的世界二. 什么是普通密码?三. 什么是商用密码?四. 普通密码 vs 商用密码:核心区别五. 选择合适的密码方案六. 结语 前言 肝文不易,点个免费的赞和…...

MYSQL中的分库分表及产生的分布式问题
分库分表是分布式数据库架构中常用的优化手段,用于解决单库单表数据量过大、性能瓶颈等问题。其核心思想是将数据分散到多个数据库(分库)或多个表(分表)中,以提升系统的吞吐量、查询性能和可扩展性。 一&am…...
拥塞控制算法cubic 和bbr
1. 背景 CUBIC 和 BBR 是两种用于网络流量控制的拥塞控制算法,广泛应用于传输中,本质上是用于提升网络速度、稳定性和效率的方案。CUBIC 和 BBR 在本质思想、设计目标和工作方式上存在很大的差异,以下是两者的详细对比。 1.1 CUBIC 提出者…...