【算法思想04】二分查找

文章目录

  • 1. 基本思想与实现
    • 1.1 基本思想
    • 1.2 值m的计算方式
    • 1.3 查找失败时的返回值
    • 1.4 代码实现
      • 1.4.1 循环
      • 1.4.2 递归
  • 2. 性能分析
    • 2.1 时间复杂度
    • 2.2 与顺序查找的效率比较
  • 3. 应用
    • 3.1 前提
    • 3.2 变体
      • 3.2.1 最基本的二分查找
      • 3.2.2 寻找左侧边界的二分查找
      • 3.2.3 寻找右侧边界的二分查找
      • 3.2.4 三种二分查找的实现代码
    • 3.3 注意事项
  • 4. 例题
    • 4.1 [二分查找(704简单)](https://leetcode.cn/problems/binary-search/)
    • 4.2 [X的平方根(69简单)](https://leetcode.cn/problems/sqrtx/)
    • 4.3 [寻找比目标字母大的最小字母(744简单)](https://leetcode.cn/problems/find-smallest-letter-greater-than-target/)
    • 4.4 [有序数组中的单一元素(540中等)](https://leetcode.cn/problems/single-element-in-a-sorted-array/)
    • 4.5 [第一个错误的版本(278简单)](https://leetcode.cn/problems/first-bad-version/)
    • 4.6 [寻找旋转排序数组中的最小值(153中等)](https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array/)
    • 4.7 [在排序数组中查找元素的第一个和最后一个位置(34中等)](https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/)
    • 4.8 [寻找峰值(162中等)](https://leetcode.cn/problems/find-peak-element/)
    • 4.9 [修车最少时间(2594中等)](https://leetcode.cn/problems/minimum-time-to-repair-cars/description/)
  • 参考资料


1. 基本思想与实现

1.1 基本思想

二分查找是一种在有序数组中查找目标元素的算法,又称折半查找。

通过将数组分成两部分,并比较目标元素与数组中间元素的大小,来确定目标元素在哪一部分中。

  • 如果目标元素等于中间元素,则查找成功。
  • 如果目标元素小于中间元素,则在左半部分继续查找。
  • 如果目标元素大于中间元素,则在右半部分继续查找。

重复这个过程,直到找到目标元素或确定目标元素不存在。

Input : [1,2,3,4,5]
target : 3
return the index : 2

1.2 值m的计算方式

  • m = (l + h) / 2,可能出现加法溢出,即l+h的结果超出整型的表示范围。
  • m = l + (h - l) / 2,建议使用。

1.3 查找失败时的返回值

循环退出时如果仍然没有查找到target,那么表示查找失败。可以有两种返回值:

  • -1:以一个错误码表示没有查找到target
  • l:将target插入到nums中的正确位置

1.4 代码实现

1.4.1 循环

框架:

public int binarySearch(int[] nums, int target) {int l = 0, h = ...;while (...) {int m = l + (h - l) / 2;if (nums[m] == target) {	//	查找成功...} else if (nums[m] > target) {	//	查找左半区间h = ...;} else if (nums[m] < target) {	//	查找右半区间l = ...;}}return ...;
}

基本实现:

public int binarySearch(int[] nums, int target) {//	1. 设置查找区间int l = 0, h = nums.length - 1;//	2. 若查找区间[l, h]不存在,则查找失败while (l <= h) {//	3. 取中间元素nums[m]与目标元素target比较大小int m = l + (h - l) / 2;if (nums[m] == target) {	//	3.1 查找成功return m;} else if (nums[m] > target) {	//	3.2 查找左半区间h = m - 1;} else {	//	3.3	查找右半区间l = m + 1;}}return -1;
}

1.4.2 递归

基本实现:

public int recursiveBinarySearch(int[] nums, int l, int h, int target) {//	1. 若查找区间[l, h]不存在,则查找失败if (l > h) return -1;//	2. 取中间元素nums[m]与目标元素target比较大小int m = l+ (h - l) / 2;if (nums[m] == target) {	//	2.1 查找成功return m;} else if (nums[m] > target) {	//	2.2 查找左半区间return recursiveBinarySearch(l, m-1, nums, target);} else {	//	2.3	查找右半区间return recursiveBinarySearch(m+1, h, nums, target);}
}

2. 性能分析

2.1 时间复杂度

二分查找的时间复杂度取决于查找成功或失败的情况。

在最好情况下,即目标元素恰好是数组中间元素,只需要进行1次比较就能找到目标元素,时间复杂度为O(1)。

在最坏情况下,即查找不到目标元素,需要进行log2(n)次比较,其中n是数组的长度,时间复杂度为O(log n)。

平均情况下,二分查找的时间复杂度也是O(log n)。

2.2 与顺序查找的效率比较

效率比较
顺序查找的平均时间复杂度为O(n),因此二分查找性能更优。

3. 应用

3.1 前提

二分查找只适用于有序数组。如果数组无序,需要先进行排序操作,然后再进行二分查找。

3.2 变体

3.2.1 最基本的二分查找

  • 初始化h = nums.length - 1;
  • 查找区间[l, h]
  • 循环终止条件while (l <= h)
  • 区间收缩l=m+1;h=m-1;
  • nums[m] == target时可立即返回

局限性:对nums = [1, 3, 3, 3, 4]target = 3的情况,会返回索引2,无法求得target的左侧边界1和右侧边界3。

3.2.2 寻找左侧边界的二分查找

  • 初始化h = nums.length;
  • 查找区间[l, h)
  • 循环终止条件while (l < h)
  • 区间收缩l=m+1;h=m;
  • nums[m] == target时不要立即返回,收缩右侧边界以锁定左侧边界,返回left

3.2.3 寻找右侧边界的二分查找

  • 初始化h = nums.length;
  • 查找区间[l, h)
  • 循环终止条件while (l < h)
  • 区间收缩l=m+1;h=m;
  • nums[m] == target时不要立即返回,收缩左侧边界以锁定右侧边界。因收缩左侧边界执行了l = m + 1,因此返回左侧边界时需要-1;因查找区间为左闭右开,因此返回右侧边界时也需要-1。

查找区间的开闭情况、循环终止条件是否包含等号都取决于h的初始化值。

3.2.4 三种二分查找的实现代码

public int binarySearch(int[] nums, int target) {int l = 0, h = nums.length - 1;while (l <= h) {int m = l + (h - l) / 2;if (nums[m] == target) {// 直接返回return m;} else if (nums[m] > target) {h = m - 1;} else {l = m + 1;}}// 直接返回return -1;
}public int leftBound(int[] nums, int target) {int l = 0, h = nums.length - 1;while (l <= h) {int m = l + (h - l) / 2;if (nums[m] == target) {// 不返回,收缩右边界,锁定左边界h = m - 1;} else if (nums[m] > target) {h = m - 1;} else {l = m + 1;}}// 检查l越界的情况if (l >= nums.length || nums[l] != target) return -1;return l;
}public int rightBound(int[] nums, int target) {int l = 0, h = nums.length - 1;while (l <= h) {int m = l + (h - l) / 2;if (nums[m] == target) {// 不返回,收缩左边界,锁定右边界l = m + 1;} else if (nums[m] > target) {h = m - 1;} else {l = m + 1;}}// 检查r越界的情况if (r < 0 || nums[r] != target) return -1;return r;
}

3.3 注意事项

  • 边界值的判断,例如h=m-1还是h=m
  • 查找区间的开闭情况,lh的更新完全取决于查找的区间
  • 循环终止条件,例如应该使用l<h还是l<=h
  • 返回值,例如应该返回l、返回m、还是返回h

4. 例题

以下例题的题解皆使用基于循环的二分查找实现。

4.1 二分查找(704简单)

4.2 X的平方根(69简单)

题目描述

即使用二分查找在区间[0,x]中查找x的平方根
class Solution {public int mySqrt(int x) {if (x <= 1) return x;int l = 1, h = x;while (l <= h) {int m = l + (h-l) / 2;if (x/m == m) {	// 使用m*m会溢出return m;} else if (x/m < m) {   // target in [l, m-1]h = m - 1;} else {    // target in [m+1, h]l = m + 1;}}return h;   // 退出循环的条件是l>h,所以此时h总是小于l的,因此返回h而不是返回l}
}

4.3 寻找比目标字母大的最小字母(744简单)

题目描述

class Solution {public char nextGreatestLetter(char[] letters, char target) {int l = 0, h = letters.length - 1;while (l <= h) {if (letters[l] > target) return letters[l];int m = l + (h-l) / 2;if (letters[m] <= target) {	// target in [m+1, h]l = m + 1;} else {	// target in [l, m]h = m;}}return letters[0];}
}

4.4 有序数组中的单一元素(540中等)

题目描述

令target为单一元素在数组中的位置
在target之后,数组中原来存在的成对状态被改变如果m为偶数
当m + 1 < index,有nums[m] == nums[m+1]
当m + 1 >= index,有nums[m] != nums[m+1]
class Solution {public int singleNonDuplicate(int[] nums) {int l = 0,h = nums.length - 1;while (l < h) {// 保证l、m、h都在偶数位,使得查找区间的长度为奇数if (m % 2 == 1) m--;if (nums[m] == nums[m+1]) { // target in [m+2, h]l = m + 2;} else {    // target in [l, m]h = m;}}// l=h,不返回nums[m]return nums[l];}
}

4.5 第一个错误的版本(278简单)

题目描述

使用二分查找,找到[false,false,...,false,true,true,...true]中第一个true的下标
/* The isBadVersion API is defined in the parent class VersionControl.boolean isBadVersion(int version); */public class Solution extends VersionControl {public int firstBadVersion(int n) {int l = 1, h = n;while (l <= h) {int m = l + (h-l) / 2;if (isBadVersion(m)) {  // target in [l, m-1]h = m - 1;} else {    // target in [m+1, h]l = m + 1;}}return l;}
}

4.6 寻找旋转排序数组中的最小值(153中等)

题目描述

class Solution {public int findMin(int[] nums) {int l = 0, h = nums.length-1, m=0;while (l < h) {m = l + (h-l) / 2;if (nums[m] > nums[h]) {   // target in [m+1, h]l = m + 1;} else {    // target in [l, m]h = m;}}return nums[l];}
}

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

题目描述

使用二分查找的方式缩小区间,查找数组nums中元素的值都为target的子区间[l, h]
class Solution {public int[] searchRange(int[] nums, int target) {int l = 0, h = nums.length - 1;while (l <= h) {int m = l + (h-l) / 2;if (nums[m] < target) { // target in [m+1,h]l = m + 1;} else if (nums[m] > target) {  // target in [l,m-1]h = m - 1;} else {    // nums[m] == targetif (nums[l] == nums[h]) {if (nums[l] == target) return new int[]{l,h};else return new int[]{-1,-1};}if (nums[l] < target) l++;if (nums[h] > target) h--;}}return new int[]{-1,-1};}
}

4.8 寻找峰值(162中等)

题目描述

对于所有有效的 i 都有 nums[i] != nums[i + 1]
.
由提示知相邻元素的值不相等,寻找数组中的极大值(该值也可能是边界值)
input: [1,2,3] - > output: 2
class Solution {public int findPeakElement(int[] nums) {int l = 0, h = nums.length - 1;while (l < h) {int m = l + (h-l) / 2;if (nums[m] < nums[m+1]) {  // target in [m+1, h]l = m + 1;} else {   // target in [l, m]h = m;}}return l;}
}

4.9 修车最少时间(2594中等)

题目描述

枚举时间 t 能都修完所有汽车。假设最少时间为 t,则
时间大于等于 t 都可以修完,否则都修不完,所以 t 的值域具有单调性,可以枚举 t 并使用二分查找。
.
在ranks[i]*n^2的时间内可以修完n辆车 -> 机械工i的效率为1/ranks[i]
随机取一个机械工修完所有车的时间作为上界
class Solution {public long repairCars(int[] ranks, int cars) {long left = 0, right = 1l * ranks[0]*cars*cars; // 防止溢出long mid = 0;while (left < right) {mid = left + (right - left) / 2;if (check(ranks, cars, mid)) {  // 判断mid分钟内机械工们能否修完carsright = mid;    // 能修完。移动上界} else {left = mid + 1; // 修不完。移动下界}}return left;    // 一定是执行left=mid+1后才跳出循环的}private boolean check(int[] ranks, int cars, long mid) {long cnt = 0;for (int x : ranks) {   // 累计每个机械工在mid分钟内修完的汽车数目cnt += (long) Math.sqrt(mid / x);}return cnt >= cars;}
}

参考资料

  • Leetcode 题解 - 二分查找
  • 图文并茂带你入门二分查找算法
  • labuladong的算法小抄 - 我写了首诗,把二分搜索算法变成了默写题

撰写于2024年1月16日凌晨2时

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

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

相关文章

【brainpan靶场渗透】

文章目录 一、基础信息 二、信息收集 三、反弹shell 四、提权 一、基础信息 Kali IP&#xff1a;192.168.20.146 靶机 IP&#xff1a;192.168.20.155 二、信息收集 似乎开放了9999&#xff0c;10000端口&#xff0c;访问页面没有太多内容&#xff0c;扫描一下目录 dirs…

matlab reshape permute

1.reshape 将向量按照顺序重新构建 矩阵&#xff0c;新矩阵 先排完第一列&#xff0c; 再第二列… 2.permute 将向量 维度变换

comctl32.dll没有被指定在window运行怎么解决?

一、文件丢失问题&#xff1a;comctl32.dll没有被指定在Windows上运行怎么解决&#xff1f; comctl32.dll是Windows操作系统中的一个重要组件&#xff0c;它负责提供用户界面元素&#xff0c;如按钮、对话框和列表视图等。当系统提示“comctl32.dll没有被指定在Windows上运行”…

Qt下使用AES进行字符串加密解密

文章目录 前言一、获取QAESEncryption库二、加密与解密实现三、示例完整代码四、下载链接总结 前言 引用&#xff1a;AES&#xff08;Advanced Encryption Standard&#xff09;是一种对称加密算法&#xff0c;被广泛用于数据加密&#xff0c;提供128、192、256位三种密钥长度&…

docker 安装minio

docker pull minio/minio #启动 mkdir -p /root/minio/config mkdir -p /root/minio/datadocker run -d \--name minio \-p 9002:9000 \-p 9001:9001 \--restartalways \-v /root/minio/data:/data \-v /root/minio/config:/root/.minio \-e "MINIO_ACCESS_KEYminioadmin…

Linux系统下安装配置 Nginx 超详细图文教程

一、下载Nginx安装包 nginx官网&#xff1a;nginx: download[这里是图片001]http://nginx.org/en/download.html 找到我们所需要版本&#xff0c;把鼠标移动到上面&#xff0c;右键打开链接进行下载 或者如果Linux联网&#xff0c;直接在Linux服务上使用wget命令把Nginx安装包…

爬虫与反爬虫实现全流程

我选取的网页爬取的是ppt nba版 需要的工具:pycharm,浏览器 爬虫需要观察它的网页信息,然后开始首先爬取它的html,可以看到有人气,标题,日期,咨询 可以看到用get方法 import requests url"https://img-home.csdnimg.cn/images/20230724024159.png?origin_urlhttps%3A%2…

最新版Edge浏览器加载ActiveX控件技术——alWebPlugin中间件V2.0.28-迎春版发布

allWebPlugin简介 allWebPlugin中间件是一款为用户提供安全、可靠、便捷的浏览器插件服务的中间件产品&#xff0c;致力于将浏览器插件重新应用到所有浏览器。它将现有ActiveX控件直接嵌入浏览器&#xff0c;实现插件加载、界面显示、接口调用、事件回调等。支持Chrome、Firefo…

OSPFv2协议状态切换(状态机)基本原理-RFC2328

个人认为&#xff0c;理解报文就理解了协议。通过报文中的字段可以理解协议在交互过程中相关传递的信息&#xff0c;更加便于理解协议。 自动换行 OSPFv2&#xff1a; 关于 OSPFv2 协议基本原理&#xff0c;可参考RFC2328-OSPF Version 2。 其他相关资料可参考&#xff1a; …

【最新】沃德协会管理系统源码+uniapp前端+环境教程

一.系统介绍 一款基于FastAdminThinkPHPUniapp开发的商协会系统&#xff0c;新一代数字化商协会运营管理系统&#xff0c;以“智慧化会员体系、智敏化内容运营、智能化活动构建”三大板块为基点&#xff0c;实施功能全场景覆盖&#xff0c;一站式解决商协会需求壁垒&#xff0…

【LeetCode: 83. 删除排序链表中的重复元素 + 链表】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

算法练习——模拟题

前言&#xff1a;模拟题的特点在于没有什么固定的技巧&#xff0c;完全考验自己的代码能力&#xff0c;因此有助于提升自己的代码水平。如果说一定有什么技巧的话&#xff0c;那就是有的模拟题能够通过找规律来简化算法。 一&#xff1a;替换所有问号 题目要求&#xff1a; 解…

Idea创建JDK17的maven项目失败

Idea创建JDK17的maven项目失败 Error occurred during initialization of VM Could not find agent library instrument on the library path, with error: Can’t find dependent libraries Possible solution: Check your maven runner VM options. Open Maven Runner setti…

VSCode设置Playwright教程

1.安装扩展 打开VS Code&#xff0c;在扩展—>搜索"Playwright Test for VSCode"&#xff0c;点击安装 按快捷键CommandShiftP&#xff0c;输入install playwright&#xff0c;点击安装Playwright 安装成功会有如下提示 2.调试脚本 打开tests/example.spec.ts文…

HTML新特性|01 音频视频

音频 1、Audio (音频) HTML5提供了播放音频文件的标准 2、control(控制器) control 属性供添加播放、暂停和音量控件 3、标签: <audio> 定义声音 <source> 规定多媒体资源,可以是多个<!DOCTYPE html> <html lang"en"> <head><…

【深度学习】卷积网络代码实战ResNet

ResNet (Residual Network) 是由微软研究院的何凯明等人在2015年提出的一种深度卷积神经网络结构。ResNet的设计目标是解决深层网络训练中的梯度消失和梯度爆炸问题&#xff0c;进一步提高网络的表现。下面是一个ResNet模型实现&#xff0c;使用PyTorch框架来展示如何实现基本的…

雷电「模拟器」v9 最新清爽去广

前言 雷电模拟器9是基于安卓9内核开发的全新版本模拟器 安装环境 [名称]&#xff1a;雷电「模拟器」 [大小]&#xff1a;579MB [版本]&#xff1a;9.1.34 [语言]&#xff1a;简体中文 [安装环境]&#xff1a;Windows 通过网盘分享的文件&#xff1a;雷电模拟器 链接:…

大模型 API 接入初探

文章目录 大模型 API 接入初探一、使用大模型 API 的前置步骤&#xff08;一&#xff09;注册账户与获取凭证&#xff08;二&#xff09;理解 API 文档 二、三个常用 API&#xff08;一&#xff09;列出模型&#xff08;二&#xff09;FIM 补全&#xff08;三&#xff09;对话补…

实时在线翻译谷歌插件

Real - time Translation插件的安装 1、下载插件并解压 2、打开谷歌浏览器&#xff0c;在地址栏输入 “chrome://extensions/” 进入扩展程序页面. 3、开启页面右上角的 “开发者模式”. 4、点击 “加载已解压的扩展程序” 按钮&#xff0c;选择之前解压的文件夹&#xff0c;点…

[数据集][图像分类]常见鱼类分类数据集2w张8类别

数据集类型&#xff1a;图像分类用&#xff0c;不可用于目标检测无标注文件 数据集格式&#xff1a;仅仅包含jpg图片&#xff0c;每个类别文件夹下面存放着对应图片 图片数量(jpg文件个数)&#xff1a;7554&#xff08;剩余1w多为测试集&#xff09; 分类类别数&#xff1a;…