算法(3)——二分查找

news2025/1/20 3:49:33

一、什么是二分查找

二分查找也称折半查找,是在一组有序(升序/降序)的数据中查找一个元素,它是一种效率较高的查找方法。

二、二分查找的原理

1、查找的目标数据元素必须是有序的。没有顺序的数据,二分法就失去意义。


2、数据元素通常是数值型,可以比较大小。


3、将目标元素和查找范围的中间值做比较(如果目标元素=中间值,查找结束),将目标元素分到较大/或者较小的一组。


4、通过分组,可以将查找范围缩小一半。


5、重复第三步,直到目标元素=新的范围的中间值,查找结束。

三、二分查找模板 

1、朴素二分查找模板

2、一般二分查找模板

四、二分查找经典OJ题

4、1 二分查找

704. 二分查找 - 力扣(LeetCode)

1、题目描述

2、算法思路

a. 定义 left right 指针,分别指向数组的左右区间。
b. 找到待查找区间的中间点 mid ,找到之后分三种情况讨论:
        i. arr[mid] == target 说明正好找到,返回 mid 的值

        ii. arr[mid] > target 说明 [mid, right] 这段区间都是⼤于 target 的,因此舍去右边区间,在左边 [left, mid -1] 的区间继续查找,即让 right = mid - 1 ,然后重复 2 过程;

        iii. arr[mid] < target 说明 [left, mid] 这段区间的值都是⼩于 target 的,因此舍去左边区间,在右边 [mid + 1, right] 区间继续查找,即让 left = mid + 1 ,然后重复 2 过程;
c. left right 错开时,说明整个区间都没有这个数,返回 -1

3、算法代码

class Solution {
public:
    int search(vector<int>& nums, int target) 
    {
        int left=0,right=nums.size()-1;
        while(left<=right)
        {
            int mid=left+(right-left)/2;
            if(nums[mid]>target)
            {
                right=mid-1;
            }
            else if(nums[mid]<target)
            {
                left=mid+1;
            }
            else{
                return mid;
            }
        }
        return -1;
    }
};

4、2 在排序数组中查找元素的第⼀个和最后⼀个位置

34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode)

1、题目描述:

2、算法思路:

⽤的还是⼆分思想,就是根据数据的性质,在某种判断条件下将区间⼀分为⼆,然后舍去其中⼀个
区间,然后再另⼀个区间内查找;
⽅便叙述,⽤ x 表⽰该元素, resLeft 表⽰左边界, resRight 表⽰右边界。
寻找左边界:
我们注意到以左边界划分的两个区间的特点:
左边区间 [left, resLeft - 1] 都是⼩于 x 的;
右边区间(包括左边界) [resLeft, right] 都是⼤于等于 x 的;
因此,关于 mid 的落点,我们可以分为下⾯两种情况:
当我们的 mid 落在 [left, resLeft - 1] 区间的时候,也就是 arr[mid] < target 。说明 [left, mid] 都是可以舍去的,此时更新 left mid + 1 的位置, 继续在 [mid + 1, right] 上寻找左边界;
mid 落在 [resLeft right] 的区间的时候,也就是 arr[mid] >= target 。 说明 [mid + 1, right] (因为 mid 可能是最终结果,不能舍去)是可以舍去的,此时 更新 right mid 的位置,继续在 [left, mid] 上寻找左边界;
由此,就可以通过⼆分,来快速寻找左边界;
注意:这⾥找中间元素需要向下取整。
因为后续移动左右指针的时候:
左指针: left = mid + 1 ,是会向后移动的,因此区间是会缩⼩的;
右指针: right = mid ,可能会原地踏步(⽐如:如果向上取整的话,如果剩下 1,2 两个元
素, left == 1 right == 2 mid == 2 。更新区间之后, left right mid 的 值没有改变,就会陷⼊死循环)。
因此⼀定要注意,当 right = mid 的时候,要向下取整。
寻找右边界思路:
resRight 表⽰右边界;
我们注意到右边界的特点:
左边区间 (包括右边界) [left, resRight] 都是⼩于等于 x 的;
右边区间 [resRight+ 1, right] 都是⼤于 x 的;
因此,关于 mid 的落点,我们可以分为下⾯两种情况:
当我们的 mid 落在 [left, resRight] 区间的时候,说明 [left, mid - 1](mid 不可以舍去,因为有可能是最终结果) 都是可以舍去的,此时更新 left mid 的位置;
当 mid 落在 [resRight+ 1, right] 的区间的时候,说明 [mid, right] 内的元素 是可以舍去的,此时更新 right mid - 1 的位置;
由此,就可以通过⼆分,来快速寻找右边界;
注意:这⾥找中间元素需要向上取整。
因为后续移动左右指针的时候:
左指针: left = mid ,可能会原地踏步(⽐如:如果向下取整的话,如果剩下 1,2 两个元
素, left == 1 right == 2 mid == 1 。更新区间之后, left right mid 的值 没有改变,就会陷⼊死循环)。
右指针: right = mid - 1 ,是会向前移动的,因此区间是会缩⼩的; 因此⼀定要注意,当 right = mid 的时候,要向下取整。

3、算法代码

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) 
    {
        int begin=0;
        if(nums.size()==0) return {-1,-1};
        int left=0,right=nums.size()-1;
        while(right>left)   //找左端点
        {
            int mid=left+(right-left)/2;
            if(nums[mid]<target) left=mid+1;
            else right=mid;
        }
        if(nums[left]!=target) return {-1,-1};
        else begin=left;
        left=0,right=nums.size()-1;
        while(right>left)
        {
            int mid=left+(right-left+1)/2;
            if(nums[mid]<=target) left=mid;
            else right=mid-1;
        }
        return {begin,right};
    }
};

4、3 搜索插入位置

35. 搜索插入位置 - 力扣(LeetCode)

1、题目描述

2、算法思路

a. 分析插⼊位置左右两侧区间上元素的特点:
设插⼊位置的坐标为 index ,根据插⼊位置的特点可以知道:
[left, index - 1] 内的所有元素均是⼩于 target 的;
[index, right] 内的所有元素均是⼤于等于 target 的。
b. left 为本轮查询的左边界, right 为本轮查询的右边界。根据 mid 位置元素的信息,分析下⼀轮查询的区间:
nums[mid] >= target 时,说明 mid 落在了 [index, right] 区间上,
mid 左边包括 mid 本⾝,可能是最终结果,所以我们接下来查找的区间在 [left, mid] 上。因此,更新 right mid 位置,继续查找。
nums[mid] < target 时,说明 mid 落在了 [left, index - 1] 区间上, mid 右边但不包括 mid 本⾝,可能是最终结果,所以我们接下来查找的区间在 [mid + 1, right] 上。因此,更新 left mid + 1 的位置,继续查找。
c. 直到我们的查找区间的⻓度变为 1 ,也就是 left == right 的时候, left 或者
right 所在的位置就是我们要找的结果。

3、算法代码

class Solution {
public:
    int searchInsert(vector<int>& nums, int target) 
    {
        int left=0,right=nums.size()-1;
        while(right>left)
        {
            int mid=left+(right-left)/2;
            if(nums[mid]<target) left=mid+1;
            else right=mid;
        }
        if(nums[left]<target) return right+1;
        return right;
    }
};

4、4 X的平方根

69. x 的平方根 - 力扣(LeetCode)

1、题目描述

2、算法思路

依次枚举 [0, x] 之间的所有数 i
(这⾥没有必要研究是否枚举到 x / 2 还是 x / 2 + 1 。因为我们找到结果之后直接就返回
了,往后的情况就不会再判断。反⽽研究枚举区间,既耽误时间,⼜可能出错)
如果 i * i == x ,直接返回 x
如果 i * i > x ,说明之前的⼀个数是结果,返回 i - 1
由于 i * i 可能超过 int 的最⼤值,因此使⽤ long long 类型

3、算法代码

class Solution {
public:
    int mySqrt(int x) 
    {
        if(x<1) return 0;
        int left=1,right=x;
        while(right>left)
        {
            long long mid=left+(right-left+1)/2;
            if(mid*mid>x) right=mid-1;
            else left=mid;
        }
        return left;
    }
};

4、5 山峰数组的峰顶

852. 山脉数组的峰顶索引 - 力扣(LeetCode)

1、题目描述

2、算法思路

峰顶的特点:⽐两侧的元素都要⼤。
因此,我们可以遍历数组内的每⼀个元素,找到某⼀个元素⽐两边的元素⼤即可
3、算法代码
class Solution {
public:
    int peakIndexInMountainArray(vector<int>& arr) 
    {
        for(int i=1;i<arr.size()-1;i++)
        {
            if(arr[i]>arr[i-1]&&arr[i]>arr[i+1])
            {
                return i;
            } 
            
        }
        return 0;
    }
};

4、5 寻找峰值   

162. 寻找峰值 - 力扣(LeetCode)

1、题目描述

2、算法思路寻找⼆段性:

任取⼀个点 i ,与下⼀个点 i + 1 ,会有如下两种情况:
arr[i] > arr[i + 1] :此时「左侧区域」⼀定会存在⼭峰(因为最左侧是负⽆穷),那么我们可以去左侧去寻找结果;
arr[i] < arr[i + 1] :此时「右侧区域」⼀定会存在⼭峰(因为最右侧是负⽆穷),那么我们可以去右侧去寻找结果。
当我们找到「⼆段性」的时候,就可以尝试⽤「⼆分查找」算法来解决问题。
3、算法代码
class Solution {
public:
    int findPeakElement(vector<int>& nums) 
    {
        vector<int> ret;
        int left=0,right=nums.size()-1;
        while(right>left)
        {
            int mid=left+(right-left+1)/2;
            if(nums[mid]>nums[mid-1]) left=mid;
            else right=mid-1;

        }
        return left;
    }
};

4、6 寻找旋转排序数组中的最⼩值

153. 寻找旋转排序数组中的最小值 - 力扣(LeetCode)

1、题目描述

2、算法思路

题⽬中的数组规则如下图所示:

其中 C 点就是我们要求的点。
⼆分的本质:找到⼀个判断标准,使得查找区间能够⼀分为⼆。
通过图像我们可以发现, [A B] 区间内的点都是严格⼤于 D 点的值的, C 点的值是严格⼩于 D 点的值的。但是当 [C D] 区间只有⼀个元素的时候, C 点的值是可能等于 D 点的值的。
因此,初始化左右两个指针 left right :然后根据 mid 的落点,我们可以这样划分下⼀次查询的区间:
mid [A B] 区间的时候,也就是 mid 位置的值严格⼤于 D 点的值,下⼀次查询区间在 [mid + 1 right] 上;
mid [C D] 区间的时候,也就是 mid 位置的值严格⼩于等于 D 点的值,下次查询区间在 [left mid] 上。
当区间⻓度变成 1 的时候,就是我们要找的结果。
3、算法代码 
class Solution {
public:
    int findMin(vector<int>& nums) 
    {
        int tmp=nums[nums.size()-1];
        int left=0,right=nums.size()-1;
        while(right>left)
        {
            int mid=left+(right-left)/2;
            if(nums[mid]>tmp) left=mid+1;
            else right=mid;
        }
        return nums[left];
    }
};

4、7 0~n-1缺失的数字

LCR 173. 点名 - 力扣(LeetCode)

1、题目描述

2、算法思路

关于这道题中,时间复杂度为 O(N) 的解法有很多种,⽽且也是⽐较好想的,这⾥就不再赘述。
本题只讲解⼀个最优的⼆分法,来解决这个问题。
在这个升序的数组中,我们发现:
在第⼀个缺失位置的左边,数组内的元素都是与数组的下标相等的;
在第⼀个缺失位置的右边,数组内的元素与数组下标是不相等的。
因此,我们可以利⽤这个「⼆段性」,来使⽤「⼆分查找」算法。
3、算法代码
class Solution {
public:
    int takeAttendance(vector<int>& records) 
    {
        int left=0,right=records.size()-1,k=0;
        while(right>left)
        {
            int mid = left+(right-left)/2;
            if(records[mid]!=mid) right=mid;
            else left=mid+1;
        }
        return left==records[left]?left+1:left;
    }

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

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

相关文章

推荐系统中 排序策略 CTR 动态加权平均法

CTR&#xff08;Click-Through Rate&#xff09;动态加权平均法是一种用于计算广告点击率的方法&#xff0c;其中每个点击率被赋予一个权重&#xff0c;这个权重可以随着时间、事件或其他因素而动态调整。这种方法旨在更灵活地反映广告点击率的变化&#xff0c;使得最近的数据更…

HTML与CSS

目录 1、HTML简介 2、CSS简介 2.1选择器 2.1.1标签选择器 2.1.2类选择器 2.1.3层级选择器(后代选择器) 2.1.4id选择器 2.1.5组选择器 2.1.6伪类选择器 2.2样式属性 2.2.1布局常用样式属性 2.2.2文本常用样式属性 1、HTML简介 超文本标记语言HTML是一种标记语言&…

【GoLang】Go语言几种标准库介绍(三)

文章目录 前言几种库debug 库 (各种调试文件格式访问及调试功能)相关的包和工具&#xff1a;示例 encoding (常见算法如 JSON、XML、Base64 等)常用的子包和其主要功能&#xff1a;示例 flag(命令行解析)关键概念&#xff1a;示例示例执行 总结专栏集锦写在最后 前言 上一篇&a…

Leetcode 剑指 Offer II 059. 数据流中的第 K 大元素

题目难度: 简单 原题链接 今天继续更新 Leetcode 的剑指 Offer&#xff08;专项突击版&#xff09;系列, 大家在公众号 算法精选 里回复 剑指offer2 就能看到该系列当前连载的所有文章了, 记得关注哦~ 题目描述 设计一个找到数据流中第 k 大元素的类&#xff08;class&#xf…

使用keepalived时虚拟IP漂移注意事项

什么是Keepalived服务 keepalived是一个开源的软件项目&#xff0c;用于实现高可用性&#xff08;HA&#xff09;的网络服务器负载均衡和故障转移。它允许将多台服务器组合在一起&#xff0c;形成一个虚拟服务器集群&#xff0c;实现负载均衡和故障转移。 keepalived的核心功…

【力扣100】46.全排列

添加链接描述 class Solution:def permute(self, nums: List[int]) -> List[List[int]]:# 思路是使用回溯if not nums:return []def dfs(path,depth,visited,res):# 出递归的条件是当当前的深度已经和nums的长度一样了&#xff0c;把path加入数组&#xff0c;然后出递归if …

echarts 二分图布局_力向导图_关系图

Echarts 常用各类图表模板配置 注意&#xff1a; 这里主要就是基于各类图表&#xff0c;更多的使用 Echarts 的各类配置项&#xff1b; 以下代码都可以复制到 Echarts 官网&#xff0c;直接预览&#xff1b; 图标模板目录 Echarts 常用各类图表模板配置一、力向导图(二分图布局…

摩尔线程S80对于软件的支持

摩尔线程对软件的支持 时间&#xff1a;2024年1月1日 显卡型号&#xff1a;MTT S80 主板型号&#xff1a;七彩虹 igame z590 火神 V20 CPU&#xff1a; intel core i5 10400f 内存&#xff1a; 海盗船3600 16*2 存储&#xff1a; 致态1Tb nvme 显卡的驱动是最新的。 游戏 S…

从 MySQL 的事务 到 锁机制 再到 MVCC

其他系列文章导航 Java基础合集数据结构与算法合集 设计模式合集 多线程合集 分布式合集 ES合集 文章目录 其他系列文章导航 文章目录 前言 一、事务 1.1 含义 1.2 ACID 二、锁机制 2.1 锁分类 2.2 隔离级别 三、MVCC 3.1 介绍 3.2 隔离级别 3.3 原理 四、总结 前…

关于Python里xlwings库对Excel表格的操作(二十五)

这篇小笔记主要记录如何【如何使用xlwings库的“Chart”类创建一个新图表】。 前面的小笔记已整理成目录&#xff0c;可点链接去目录寻找所需更方便。 【目录部分内容如下】【点击此处可进入目录】 &#xff08;1&#xff09;如何安装导入xlwings库&#xff1b; &#xff08;2…

LeetCode刷题--- 不同路径 III

个人主页&#xff1a;元清加油_【C】,【C语言】,【数据结构与算法】-CSDN博客 个人专栏 力扣递归算法题 http://t.csdnimg.cn/yUl2I 【C】 ​​​​​​http://t.csdnimg.cn/6AbpV 数据结构与算法 ​​​http://t.csdnimg.cn/hKh2l 前言&#xff1a;这个专栏主要讲述递…

二叉树详解(深度优先遍历、前序,中序,后序、广度优先遍历、二叉树所有节点的个数、叶节点的个数)

目录 一、树概念及结构(了解) 1.1树的概念 1.2树的表示 二、二叉树概念及结构 2.1概念 2.2现实中的二叉树&#xff1a; 2.3数据结构中的二叉树&#xff1a; 2.4特殊的二叉树&#xff1a; 2.5 二叉树的存储结构 2.51 顺序存储&#xff1a; 2.5.2 链式存储&…

Apache Flink连载(二十三):Flink HA - Flink基于Yarn HA

🏡 个人主页:IT贫道_大数据OLAP体系技术栈,Apache Doris,Clickhouse 技术-CSDN博客 🚩 私聊博主:加入大数据技术讨论群聊,获取更多大数据资料。 🔔 博主个人B栈地址:豹哥教你大数据的个人空间-豹哥教你大数据个人主页-哔哩哔哩视频 目录 1. Yarn HA配置 ​​​​…

婴幼儿家庭护理百科知识,宝宝健康成长育儿实用课程

一、教程描述 本套教程由具有丰富育儿经验的多名专家精心打造而成&#xff0c;也是专门提供给准爸妈们学习的实用课程&#xff0c;可以解决宝宝的日常护理、日常喂养、饮食调理、疾病防治、意外护理等多方面问题。课程不仅可以丰富你的育儿知识&#xff0c;而且能够让你把这些…

vue写了这么久了你对slot的理解是什么?slot使用场景有哪些?

一、slot是什么 在HTML中 slot 元素 &#xff0c;作为 Web Components 技术套件的一部分&#xff0c;是Web组件内的一个占位符 该占位符可以在后期使用自己的标记语言填充 举个栗子 <template id"element-details-template"><slot name"element-na…

docker应用部署(部署MySql,部署Tomcat,部署Nginx,部署Redis)

Docker 应用部署 一、部署MySQL 搜索mysql镜像 docker search mysql拉取mysql镜像 docker pull mysql:5.6创建容器&#xff0c;设置端口映射、目录映射 # 在/root目录下创建mysql目录用于存储mysql数据信息 mkdir ~/mysql cd ~/mysqldocker run -id \ -p 3307:3306 \ --na…

Plantuml之YAML效果图语法介绍(二十六)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

3D视觉-3D测量技术对比

从前面四种主流的 3D 测量技术来看&#xff0c;其优点和缺点都很明显&#xff0c;没有单独一种技术可以适用于所有的三维测量场景&#xff0c;从实际应用来看&#xff0c;双目与结构光在人脸识别&#xff0c;拆码垛定位&#xff0c;静态尺寸测量等应用上最为广泛。激光三角法因…

swing快速入门(三十四)输入对话框

&#x1f33c;注释很详细&#xff0c;直接上代码 &#x1f337;新增内容 &#x1f940;字符串输入型 输入对话框用法 &#x1f940;下拉选项输入型输入对话框用法 &#x1fab4;完整代码&#xff1a; package swing31_40;import javax.swing.*; import java.awt.*; import ja…

介绍一款PDF在线工具

PDF是我们日常工作中的一种常见格式&#xff0c;其处理也是我们工作的重要基础性环节&#xff0c;一款可靠的处理工具显得十分重要。 完全免费、易于使用、丰富的PDF处理工具&#xff0c;包括&#xff1a;合并、拆分、压缩、转换、旋转和解锁PDF文件&#xff0c;以及给PDF文件…