【优选算法篇】:揭开二分查找算法的神秘面纱--数据海洋中的精准定位器

✨感谢您阅读本篇文章,文章内容是个人学习笔记的整理,如果哪里有误的话还请您指正噢✨
✨ 个人主页:余辉zmh–CSDN博客
✨ 文章所属专栏:c++篇–CSDN博客

在这里插入图片描述

文章目录

  • 一.二分查找算法
  • 二.算法模板
    • 模板一
    • 模板二
    • 模板三
  • 三.例题演练
    • 1.x的平方根
    • 2.搜索插入位置
    • 3.山脉数组的峰值索引
    • 4.寻找峰值
    • 5.寻找旋转排序数组中的最小值
    • 6.点名(0到n-1中缺失的数字)

一.二分查找算法

  • 基本思想

    二分查找算法的基本思想是:首先确定目标值可能存在的区间,然后逐步缩小区间直到确定目标值的位置或者确定目标值不存在。这个过程中,每次都会将查找范围缩小一半,因此大大提高了查找效率。

  • 前提条件

    有些地方可能对于二分查找算法的前提条件要求必须是有序的待查找数组,如果数组无序则不能使用二分查找。但实际上并不是“必须有序”才能使用,只要待查找数组满足二段性,可以将数组分成两个区间,这两个区间中的子数组又是有序的,同样可以使用二分查找,在后面的一些例题中就有这样的使用。

  • 算法步骤

    这里不具体讲解,直接在后面通过模板来讲解

  • 时间复杂度和空间复杂度

    • 时间复杂度:二分查找算法的时间复杂度为O(log n),其中n是数组的长度。因为每次查找都会将范围缩小一半,所以查找的次数是log n(以2为底数的对数)。
    • 空间复杂度:因为只使用几个额外的变量(通常是left,right,mid),所以空间复杂度是O(1)。

二.算法模板

模板一

这里通过一道例题来引出模板一:朴素二分查找

题目

在这里插入图片描述

算法原理及模板一
在这里插入图片描述

代码实现

int search(vector<int>& nums,int target){
    //朴素二分查找
    int left = 0, right = nums.size() - 1;
    int mid = 0;

    while(left<=right){
        //获取中间值
        //两种写法
        mid = left + (right - left) / 2;
        //mid = left + (right - left + 1) / 2;

        if(nums[mid]<target){
            left = mid + 1;
        }
        else if(nums[mid]>target){
            right = mid - 1;
        }
        else{
            return mid;
        }
    }

    return -1;
}

模板二

这里通过一道例题来引出模板二以及模板三

题目

在这里插入图片描述

算法原理及模板二

在这里插入图片描述

模板三

算法原理及模板三

在这里插入图片描述

代码实现

vector<int> searchRange(vector<int> &nums, int target)
{
    // 处理特殊情况,空数组
    if (nums.empty())
    {
        return {-1, -1};
    }
    int left = 0, right = nums.size() - 1;
    int mid = 0;
    int begin = 0;

    // 查找区间的左端点
    // 细节点一:这里只能左指针小于右指针,不能等于
    while (left < right)
    {
        // 细节点二:获取中间值这里不能加一
        mid = left + (right - left) / 2;

        if (nums[mid] < target)
        {
            left = mid + 1;
        }
 
        if (nums[mid] >= target)
        {
            right = mid;
        }
    }

    // 查找左端点结束后,找到继续查找右端点,否则返回没有找到
    if (left == right && nums[left] == target)
    {
        begin = left;
    }
    else
    {
        return {-1, -1};
    }

    right = nums.size() - 1;

    // 查找区间的右端点
    // 细节点一
    while (left < right)
    {
        // 细节点二:获取中间值这里要加一
        mid = left + (right - left + 1) / 2;

        if (nums[mid] <= target)
        {
            left = mid;
        }

        if (nums[mid] > target)
        {
            right = mid - 1;
        }
    }

    return {begin, right};
}

三.例题演练

1.x的平方根

题目

在这里插入图片描述

算法原理

从1到x的数组中:[0,x-1](【1,2,3…x】),因为返回的是整数,对于小数部分要舍去,因此要往小的数进位,比如2.82842,进位到2而不是3,所以根据二段性可以分为两个区间:假设目标值下标为s,[0,s]区间中的值的平方都小于等于x,(s,x-1]区间中的值的平方都大于x,使用模板三。

代码实现

int mySqrt(int x) {
    if(x<1){
        return 0;
    }

    int left=1,right=x;
    long long mid=0;

    while(left<right){
        mid=left+(right-left+1)/2;

        if(mid*mid<=x){
            left=mid;
        }
        else{
            right=mid-1;
        }
    }

    return left;
}

2.搜索插入位置

题目

在这里插入图片描述

算法原理

因为可能存在目标值不存在的情况,所以不能使用朴素二分查找模板,这里只能使用模板二或者模板三,这里以模板三为例。

根据二段性可以将数组分为两个区间,假设目标值下标为s,[0,s]区间的值小于等与目标值,(s,n-1)区间的值大于目标值。

当出现目标值不存在的情况时,找到的一定是小于目标值的下标位置,直接返回该位置的下一个即可。

代码实现

int searchInsert(vector<int>& nums,int target){
    int left=0,right=nums.size()-1;
    int mid;

    while(left<right){
        mid = left + (right - left + 1) / 2;

        if(nums[mid]<=target){
            left = mid;
        }
        else{
            right = mid - 1;
        }
    }

    if(nums[left]<target){
        return left+1;
    }
    return left;
}

3.山脉数组的峰值索引

题目

在这里插入图片描述

算法原理

在这道题中,如果第一眼看数组不是有序的,可能就会觉得不能使用二分查找算法来解,但是,我们仔细观察可以发现,在这道题中,根据题目要求可以将数组分成两端,也就使数组具有二段性,因此这道题实际上还是使用二分查找算法来解。

假设峰值是s,s左边的值[0,s)全都有序,也就是升序排列,前一个值小于后一个值,而s右边的值(s,n-1]也全都有序,但是是降序排列,前一个值大于后一个值

根据二段性就可以使用模板,这里用模板三来演示。

代码实现

int peakIndexInMountainArray(vector<int>& arr){
    int left = 0, right = arr.size() - 1;
    int mid = 0;

    while(left<right){
        mid = left + (right - left + 1) / 2;

        if(arr[mid-1]<arr[mid]){
            left = mid;
        }
        else{
            right = mid - 1;
        }
    }

    return left;
}

4.寻找峰值

题目

在这里插入图片描述

算法原理

这道题和上面的山脉数组的峰值算法原理一样,同样可以使用相同的模板来解。唯一不同的是,这道题中的数组可能有多个峰值,但我们只需找到一个返回即可。

代码实现

int findPeakElement(vector<int>& nums){
    int left = 0, right = nums.size() - 1;
    int mid=0;

    while(left<right){
        mid = left + (right - left + 1) / 2;

        if(nums[mid-1]<nums[mid]){
            left = mid;
        }
        else{
            right = mid - 1;
        }
    }

    return left;
}

5.寻找旋转排序数组中的最小值

题目

在这里插入图片描述

算法原理
在这道题中,给定的数组同样不满足有序的条件,但是根据题意可以发现,数组满足二段性。

假设最小值下标为s,最大值左边区间的值[0,s),全都小于最大值;最小值右边区间的值[s,n-1]全都小于数组第一个值(下标为0);因为数组第一个值在左边区间中是最小的,左边区间的值又全都大于右边区间的最后一个值(下标为n-1),也就是数组最后一个值。

根据二段性可以使用模板二或者模板三,使用不同的模板时,要处理不同的细节。

使用模板二,就是以最后一个元素作为区分值,最小值左边元素都大于最后一个元素,最小值右边都小于等于最后一个元素,直接找最小值。

使用模板三,就是以第一个元素为区分值,最小值元素左边都大于等于第一个元素,最小值右边都小于第一个元素,找最大值,最小值就是最大值后一个,因此循环结束后,还要将下标再加一才是最小值的下标。

代码实现

int findMin(vector<int>& nums){
    int left = 0, right = nums.size() - 1;

    //利用二段性,以最后一个元素为区分值
    //最小值左边元素都大于最后一个元素,最小值右边都小于等于最后一个元素
    //直接找最小值
    while(left<right){
        int mid = left + (right - left) / 2;

        if(nums[mid]>nums[nums.size()-1]){
            left = mid + 1;
        }
        else{
            right = mid;
        }
    }

    //以第一个元素为区分值
    //最小值元素左边都大于等于第一个元素,最小值右边都小于第一个元素
    //找最大值,最小值就是最大值后一个
    while(left<right){
        int mid = left + (right - left + 1) / 2;

        if(nums[mid]>=nums[0]){
            left = mid;
        }
        else{
            right = mid - 1;
        }
    }
    if(left!=nums.size()-1){
        left++;
    }
    
    
    return nums[left];
}

6.点名(0到n-1中缺失的数字)

题目

在这里插入图片描述

算法原理
这道题相当于从0当n-1中,查找缺失的数字,仔细看我们可能看不出来有什么二段性质,但是如果我们将每个值都和下标对比,就是发现二段性。

这里以缺失数字4的数组为例:

【0,1,2,3,5,6】

下标:【0,1,2,3,4,5】

从上面我们可以看出,缺失数字的下标左边区间的下标对应数组中的数字,而缺失数字右边区间的下标则是对应数字减一。

根据上面的二段性就可以使用模板二。

注意还有一种情况可能是:【0,1,2,3】数组有序,这时缺失的数字就是4,因此在循环结束后,要进行一个判断,如果当前下标是最后一个,且下标对应数组中的数字,这时下标要加一返回。

代码实现

int takeAttendance(vector<int>& nums){
    int left = 0, right = nums.size() - 1;

    while(left<right){
        int mid = left + (right - left) / 2;

        if(nums[mid]==mid){
            left = mid + 1;
        }
        else{
            right = mid;
        }
    }

    if(left==nums.size()&&nums[left]==left){
        left++;
    }

    return left;
}

以上就是关于二分查找算法以及例题的讲解,如果哪里有错的话,可以在评论区指正,也欢迎大家一起讨论学习,如果对你的学习有帮助的话,点点赞关注支持一下吧!!!
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

PlantUML——类图

背景 类图是UML模型中的静态视图&#xff0c;其主要作用包括&#xff1a; 描述系统的结构化设计&#xff0c;显示出类、接口以及它们之间的静态结构和关系。简化对系统的理解&#xff0c;是系统分析与设计阶段的重要产物&#xff0c;也是系统编码和测试的重要模型依据。 在U…

来也RPA程序异常处理

1、程序异常模块怎么弄&#xff1a;连接第一个流程块后&#xff0c;连接第二个流程块就是虚线异常块。这是编辑器固定的功能。 2、异常模块做什么&#xff1f;系统会自动把异常文本&#xff0c;通输入参数 $BlockInput 传入异常流程块。 然后&#xff0c;这个异常文本&#xf…

电子应用设计方案-43:智能手机充电器系统方案设计

智能手机充电器系统方案设计 一、引言 随着智能手机的广泛应用&#xff0c;对充电器的性能、效率和安全性提出了更高的要求。本方案旨在设计一款高效、安全、兼容多种快充协议的智能手机充电器。 二、系统概述 1. 系统目标 - 提供快速、稳定、安全的充电功能。 - 兼容主流的智…

Java Agent(一)、 初步认识Instrumentation

目录 1、什么是Instrumentation&#xff1f; 2、底层机制 2.1、工作流程 2.2、Instrumentation API 3、加载Java Agent 3.1、静态Agent示例 3.1.1、定义一个agent 3.1.2、配置 MANIFEST.MF 3.1.3、定义main测试类 3.1.4、启动参数添加-javaagent 3.2、动态Agent示例…

关于SpringBoot项目创建后构建总是失败的问题

第一个问题&#xff1a;IDEA创建项目总是失败 原因&#xff1a;创建项目的时候默认使用的是https://start.spring.io&#xff0c;这个是一个外国网站&#xff0c;众所周知的就是国内访问总是出现不稳定的现象&#xff0c;这就是导致项目创建失败的最终原因。 解决方法&#x…

Java-自动拆箱/装箱/缓存/效率

为什么基本类型需要包装类&#xff1f; 泛型与集合支持问题&#xff1a;基本数据类型在使用上虽然方便、简单且高效&#xff0c;但像泛型以及集合元素的存储等场景并不支持基本数据类型&#xff0c;而包装类可以解决这个问题&#xff0c;使其能更好地融入到一些需要对象类型的…

计算机毕业设计Python中华古诗词知识图谱可视化 古诗词智能问答系统 古诗词数据分析 古诗词情感分析模型 自然语言处理NLP 机器学习 深度学习

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

微服务网关SpringCloudGateway、Kong比较

网关产品 1. Spring Cloud Gateway 基本信息 Spring Cloud Gateway是Spring Cloud生态系统中的一个组件&#xff0c;基于Spring 5、Project Reactor和Spring Boot 2构建。它旨在为微服务架构提供一种简单而有效的API网关解决方案。 功能特点 路由功能强大&#xff1a;使用Rou…

实现基于分布式的LAMP架构+NFS实时同步到备份服务器

概述 项目计划用WordPress搭建一个博客系统, 为了性能更好,两个服务器都对外提供WordPress博客系统服务, 数据放在MySQL服务器, 有些上传的图片发送到NFS服务器上&#xff0c;并且把NFS数据实时同步到一个备份服务器上。 服务名称IP地址DNS10.0.0.200WEB110.0.0.201W…

【NVIDIA orin nx 安装ultralytics yolov11】

注意:不同用户安装的python可能会在不同的路径,因此不同的pip管理会导致安装的 torch和torchvision会在不同的路径下 记得区分用户来运行yolo 一、确认系统 JetPack 版本 此处使用5.1.1 1、查看JetPack 版本 jtop二、安装 ultralytics、pytorch、torchvision、onnxruntime…

Linux系统挂载exfat格式U盘教程,触觉智能RK3562开发板演示

本文介绍Linux系统&#xff08;Ubuntu/Debian通用&#xff09;挂载exfat格式U盘的方法&#xff0c;触觉智能RK3562开发板演示&#xff0c;搭载4核A53处理器&#xff0c;主频高达2.0GHz&#xff1b;内置独立1Tops算力NPU&#xff0c;可应用于物联网网关、平板电脑、智能家居、教…

【Vulkan入门】08-CreateRenderPass

目录 先叨叨git信息关键代码TestPipeLine::CreateRenderPass() 先叨叨 上篇已经为Pipeline编写好了程序&#xff08;Shader&#xff09;。接下来要为Pipeline创建RenderPass。 关于RenderPass&#xff0c;在【Vulkan入门】06-Pipeline介绍中已经作了简单的介绍。这里再详细说一…

从 HTTP 到 HTTPS 再到 HSTS

近些年&#xff0c;随着域名劫持、信息泄漏等网络安全事件的频繁发生&#xff0c;网站安全也变得越来越重要&#xff0c;也促成了网络传输协议从 HTTP 到 HTTPS 再到 HSTS 的转变。 HTTP HTTP&#xff08;超文本传输协议&#xff09; 是一种用于分布式、协作式和超媒体信息系…

01-Chromedriver下载与配置(mac)

下载地址&#xff1a; 这里我用的最后一个&#xff0c;根据自己chrome浏览器选择相应的版本号即可 ChromeDriver官网下载地址&#xff1a;https://sites.google.com/chromium.org/driver/downloads ChromeDriver官网最新版下载地址&#xff1a;https://googlechromelabs.git…

面试技术点之安卓篇

一、基础 二、高级 三、组件 Android中SurfaceView和TextureView有什么区别&#xff1f; 参考 Android中SurfaceView和TextureView有什么区别&#xff1f; 四、三方框架 五、系统源码 六、性能优化

架构13-持久化存储

零、文章目录 架构13-持久化存储 1、Kubernetes 存储设计 &#xff08;1&#xff09;存储设计考量 **设计哲学&#xff1a;**Kubernetes 遵循用户通过资源和声明式 API 描述意图&#xff0c;Kubernetes 根据意图完成具体操作。**复杂性&#xff1a;**描述用户的存储意图本身…

可视化建模以及UML期末复习----做题篇

一、单项选择题。&#xff08;20小题&#xff0c;每小题2分,共40分&#xff09; 1、UML图不包括&#xff08; &#xff09; A、用例图 B、状态机图 C、流程图 D、类图 E、通信图 答案&#xff1a;C、流程图 UML中不包括传统意义上的流程图&#xff0c;流程图通常是指B…

idea_maven详解

秒懂Maven maven简介maven安装和配置maven本地配置maven工程的GAVP创建maven工程项目结构说明项目构建说明 Maven依赖管理核心信息配置依赖管理配置依赖信息查询依赖范围设置依赖属性配置依赖下载失败错误解决Build构建配置依赖传递依赖冲突 maven工程继承继承作用应用场景继承…

vue3+vite纯前端实现自动触发浏览器刷新更新版本内容,并在打包时生成版本号文件

前言 在前端项目中&#xff0c;有时候为了实现自动触发浏览器刷新并更新版本内容&#xff0c;可以采取一系列巧妙的措施。我的项目中是需要在打包时候生成一个version.js文件&#xff0c;用当前打包时间作为版本的唯一标识&#xff0c;然后打包发版 &#xff0c;从实现对版本更…

鸿蒙实现应用通知

目录&#xff1a; 1、应用通知的表现形式2、应用通知消息的实现1、发布普通文本类型通知2、发布进度类型通知3、更新通知4、移除通知 3、设置通知道通展示不同形式通知4、设置通知组5、为通知添加行为意图1、导入模块2、创建WantAgentInfo信息3、创建WantAgent对象4、构造Notif…