算法专题(三):二分查找

本篇还是像之前一样,以举例子的形式向大家讲解!每道题的题目均是传送门!点击跳转对应题!

目录

一、二分查找 

1.1 题目

1.2 思路

1.3 代码实现

总结(模版)

朴素版:

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

2.1 题目

2.2 思路

2.3 代码实现

总结(模版)

三、搜索插入位置 

3.1 题目

3.2 思路

3.3 代码实现

四、x 的平方根

4.1 题目

4.2 思路

4.2 代码实现

暴力:

二分查找:

​编辑

五、山脉数组的峰顶索引 

5.1 题目

5.2 思路

5.3 代码实现

暴力:

二分查找:

六、寻找峰值

6.1 题目

6.2 思路

6.3 代码实现

七、寻找旋转排序数组中的最小值 

7.1 题目

7.2 思路

7.3 代码实现

八、点名(0~n-1 中缺失的数字)

8.1 题目

8.2 思路

8.3 代码实现


一、二分查找 

1.1 题目

1.2 思路

暴力解法就是,从左向右依次遍历,直到找到target,返回其下标

1. 定义 left , right 指针,分别指向数组的左右区间。
2. 找到待查找区间的中间点 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 过程;
3. 当 left 与 right 错开时,说明整个区间都没有这个数,返回 -1 。 

1.3 代码实现

直接 mid = (left + right)/2;  left+right 是有可能溢出的,所以我们改用让left向右移动一半的距离

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)left = mid + 1;else if (nums[mid] > target)right = mid - 1;else return mid;}return -1;}
};

总结(模版)

朴素版:

        while(left <= right){int mid = left + (right - left)/2;if(...)left = mid + 1;else if (...)right = mid - 1;elsereturn ...;}

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

2.1 题目

2.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 的时候,要向下取整。

2.3 代码实现

class Solution {
public:vector<int> searchRange(vector<int>& nums, int target) {if(nums.size() == 0)return {-1,-1};int begin = 0;//二分左端点int left = 0,right = nums.size()-1;while(left < right){int mid = left+(right-left)/2;if(target > nums[mid])left = mid+1;elseright = mid;}if(target != nums[left]) //判断是否有结果return {-1,-1};elsebegin = left;  //标记下一处端点right = nums.size()-1;//二分右端点while(left<right){int mid = left +(right-left+1)/2;if(target >= nums[mid])left = mid;elseright = mid-1;}return {begin,right};}
};

总结(模版)

请大家一定不要觉得背下模板就能解决所有二分问题(不要死记模版)。二分问题最重要的就是要分析题意,然后确定要搜索的区间,根据分析问题来写出二分查找算法的代码。

1. 关于什么时候用三段式,还是二段式中的某一个,一定不要强行去用,而是通过具体的问题分析情况,根据查找区间的变化确定指针的转移过程,从而选择一个模板。
2. 当选择两段式的模板时:
        在求 mid 的时候,只有 right - 1 的情况下,才会向上取整(也就是 +1 取中间数)

三、搜索插入位置 

3.1 题目

3.2 思路

1. 分析插入位置左右两侧区间上元素的特点:
        设插入位置的坐标为 index ,根据插入位置的特点可以知道:
        • [left, index - 1] 内的所有元素均是小于 target 的;
        • [index, right] 内的所有元素均是大于等于 target 的。
2. 设 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 的位置,继续查找。
3. 直到我们的查找区间的长度变为 1 ,也就是 left == right 的时候, left 或者right 所在的位置就是我们要找的结果。

3.3 代码实现

class Solution {
public:int searchInsert(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)left = mid+1;elseright = mid;}if(nums[left] < target)return left+1;return left;}
};

四、x 的平方根

4.1 题目

4.2 思路

暴力解法:

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

二分查找:

4.2 代码实现

暴力:

class Solution {
public:
int mySqrt(int x) 
{// 由于两个较大的数相乘可能会超过 int 最大范围// 因此用 long longlong long i = 0;for (i = 0; i <= x; i++){// 如果两个数相乘正好等于 x,直接返回 iif (i * i == x) return i;// 如果第一次出现两个数相乘大于 x,说明结果是前一个数if (i * i > x) return i - 1;}// 为了处理oj题需要控制所有路径都有返回值return -1;}
};

二分查找:

class Solution {
public:int mySqrt(int x) {if(x < 1) //处理边界return 0; int left = 1,right = x;while(left < right){long mid  = left + (right-left+1)/2; // 防溢出if(mid * mid <= x)left = mid;elseright = mid - 1;}return left;}
};

五、山脉数组的峰顶索引 

5.1 题目

5.2 思路

暴力枚举(时间:O(N)):

峰顶的特点:比两侧的元素都要大。
因此,我们可以遍历数组内的每一个元素,找到某一个元素比两边的元素大即可。

二分查找:

1. 分析峰顶位置的数据特点,以及山峰两旁的数据的特点:
        ◦ 峰顶数据特点: arr[i] > arr[i - 1] && arr[i] > arr[i + 1] ;
        ◦ 峰顶左边的数据特点: arr[i] > arr[i - 1] && arr[i] < arr[i + 1] ,也就是呈现上升趋势;
        ◦ 峰顶右边数据的特点: arr[i] < arr[i - 1] && arr[i] > arr[i + 1] ,也就是呈现下降趋势。
2. 因此,根据 mid 位置的信息,我们可以分为下面三种情况:
        ◦ 如果 mid 位置呈现上升趋势,说明我们接下来要在 [mid + 1, right] 区间继续搜索;
        ◦ 如果 mid 位置呈现下降趋势,说明我们接下来要在 [left, mid - 1] 区间搜索;
        ◦ 如果 mid 位置就是山峰,直接返回结果。

5.3 代码实现

暴力:

class Solution {
public:int peakIndexInMountainArray(vector<int>& arr) 
{int n = arr.size();// 遍历数组内每一个元素,直到找到峰顶for (int i = 1; i < n - 1; i++)// 峰顶满足的条件if (arr[i] > arr[i - 1] && arr[i] > arr[i + 1])return i;// 为了处理 oj 需要控制所有路径都有返回值return -1;
}
};

二分查找:

class Solution {
public:int peakIndexInMountainArray(vector<int>& arr) {   //第一个位置和最后一个位置不可能是结果int left = 1,right = arr.size()-2; while(left < right){int mid = left + (right-left+1)/2;if(arr[mid] > arr[mid-1]) left = mid;else right = mid -1;}return left;}
};

六、寻找峰值

6.1 题目

6.2 思路

寻找二段性:
        任取一个点 i ,与下一个点 i + 1 ,会有如下两种情况:
                • arr[i] > arr[i + 1] :此时「左侧区域」一定会存在山峰(因为最左侧是负无穷),那么我们可以去左侧去寻找结果;
                • arr[i] < arr[i + 1] :此时「右侧区域」一定会存在山峰(因为最右侧是负无穷),那么我们可以去右侧去寻找结果。
        当我们找到「二段性」的时候,就可以尝试用「二分查找」算法来解决问题。

6.3 代码实现

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

七、寻找旋转排序数组中的最小值 

7.1 题目

7.2 思路

关于暴力查找,只需遍历一遍数组,这里不再赘述 ,时间复杂度:O(N)

其中 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 的时候,就是我们要找的结果。

7.3 代码实现

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

八、点名(0~n-1 中缺失的数字)

8.1 题目

8.2 思路

关于这道题中,时间复杂度为 O(N) 的解法有很多种,比如哈希表,直接遍历查找,位运算,高斯求和公式。
这里就只讲解一个二分法,来解决这个问题。
在这个升序的数组中,我们发现:
        ▪ 在第一个缺失位置的左边,数组内的元素都是与数组的下标相等的;
        ▪ 在第一个缺失位置的右边,数组内的元素与数组下标是不相等的。
因此,我们可以利用这个「二段性」,来使用「二分查找」算法。

8.3 代码实现

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


本篇完,下篇见!大家一定要分析过程,不要死记模版!

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

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

相关文章

在k8s中部署一个可外部访问的Redis Sentinel

1.前提条件&#xff1a; 1.部署了multus 想要k8s外部能访问k8s内部的redis&#xff0c;redis-server启动时必须使用multus的IP 2.helm客户端安装 2.开始安装 准备3个multus ip 10.10.10.130 10.10.10.131 10.10.10.132 apiVersion: k8s.cni.cncf.io/v1 kind: NetworkAttac…

使用tritonserver完成clip-vit-large-patch14图像特征提取模型的工程化。

1、关于clip-vit-large-patch14模型 关于openapi开源的clip-vit-large-patch14模型的特征提取&#xff0c;可以参考之前的文章&#xff1a;Elasticsearch向量检索需要的数据集以及768维向量生成这篇文章详细介绍了模型的下载地址、使用方式、测试脚本&#xff0c;可以让你一步…

偏序关系.

一、偏序&#xff08;半序&#xff09;关系 偏序关系 自反反对称传递性 二、全序&#xff08;线序、链&#xff09;关系 三、偏序集中的重要元素 1. 极大元与极小元 极大元找所在集合的一个或几个最高点&#xff1b; 极小元找所在集合的一个或几个最低点。 2. 最大元与最小…

2024嵌入式系统的未来发展与技术洞察分享

时间如白驹过隙&#xff0c;不知不觉又是一年&#xff0c;这一年收获满满。接下来&#xff0c;将本年度对技术的感悟和洞察分析如下&#xff0c;希望对大家有所帮助。 在过去几十年里&#xff0c;嵌入式系统技术迅速发展&#xff0c;成为现代电子设备和智能硬件的核心组成部分。…

AQS公平锁与非公平锁之源码解析

AQS加锁逻辑 ReentrantLock.lock public void lock() {sync.acquire(1);}AbstractQueuedSynchronizer#acquire public final void acquire(int arg) {if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();}addWaiter就是将节点加入…

数字电子技术基础(十五)——MOS管的简单介绍

目录 1 MOS的简单介绍 1.1 MOS简介 1.2 MOS管的基本结构 1.3 MOS管工作时的三个区域 1.4 MOSEF的结构的工作原理 1 MOS的简单介绍 1.1 MOS简介 绝缘栅型场效应管&#xff0c;简称MOS管&#xff0c;全称为金属-氧化物-半导体场效应晶体管&#xff08;Metal-Oxide-Semic…

基础入门-传输加密数据格式编码算法密文存储代码混淆逆向保护安全影响

知识点&#xff1a; 1、传输格式&传输数据-类型&编码&算法 2、密码存储&代码混淆-不可逆&非对称性 一、演示案例-传输格式&传输数据-类型&编码&算法 传输格式 JSON XML WebSockets HTML 二进制 自定义 WebSockets&#xff1a;聊天交互较常…

Spark/Kafka

文章目录 项目地址一、Spark1. RDD1.1 五大核心属性1.2 执行原理1.3 四种创建方式二、Kafka2.1 生产者(1)分区器(2)生产者提高吞吐量(3) 生产者数据可靠性数据传递语义幂等性和事务数据有序2.2 Broker(1)Broker工作流程(2)节点服役和退役2.3 副本(1)Follower故障细…

10倍数据交付提升 | 通过逻辑数据仓库和数据编织高效管理和利用大数据

数据已经成为企业核心竞争力的关键要素。随着大数据技术的发展&#xff0c;如何高效管理和利用海量的数据&#xff0c;已成为企业在数字化转型过程中面临的重要课题。传统的数据仓库已经不能满足当今企业对数据处理的高效性、灵活性和实时性的需求。在这种背景下&#xff0c;逻…

《keras 3 内卷神经网络》

keras 3 内卷神经网络 作者&#xff1a;Aritra Roy Gosthipaty 创建日期&#xff1a;2021/07/25 最后修改时间&#xff1a;2021/07/25 描述&#xff1a;深入研究特定于位置和通道无关的“内卷”内核。 &#xff08;i&#xff09; 此示例使用 Keras 3 在 Colab 中查看 GitHub …

Unreal Engine 5 C++ Advanced Action RPG 十章笔记

第十章 Survival Game Mode 2-Game Mode Test Map 设置游戏规则进行游戏玩法 生成敌人玩家是否死亡敌人死亡是否需要刷出更多 肯定:难度增加否定:玩家胜利 流程 新的游戏模式类游戏状态新的数据表来指定总共有多少波敌人生成逻辑UI告诉当前玩家的敌人波数 3-Survival Game M…

嵌入式产品级-超小尺寸热成像相机(从0到1 硬件-软件-外壳)

Thermal_Imaging_Camera This is a small thermal imaging camera that includes everything from hardware and software. 小尺寸热成像相机-Pico-LVGL-RTOS 基于RP2040 Pico主控与RTOS&#xff0c;榨干双核性能实现LVGL和成图任务并行。ST7789驱动240280屏&#xff0c;CST8…

AI守护煤矿安全生产:基于视频智能的煤矿管理系统架构解析

前言 本文我将介绍我和我的团队自主研发设计的一款AI产品的成果展示——“基于视频AI识别技术的煤矿安全生产管理系统”。 这款产品是目前我在创业阶段和几位矿业大学的博士共同从架构设计、开发到交付的全过程中首次在博客频道发布, 我之前一直想写但没有机会来整理这套系统的…

OCCT 之 TDF_Attribute 以及子类

一.概述 TDF_Label是OCAF中核心数据结构&#xff0c;与TDF_Attribute结合使用&#xff0c;实现对模型的各种操作。 以下摘自OCCT7.7.0官方文档 A class each application has to implement. It is used to contain the application data. This abstract class, alongwith La…

数字化时代,传统代理模式的变革之路

在数字化飞速发展的今天&#xff0c;线上线下融合&#xff08;O2O&#xff09;成了商业领域的大趋势。这股潮流&#xff0c;正猛烈冲击着传统代理模式&#xff0c;给它带来了新的改变。 咱们先看看线上线下融合现在啥情况。线上渠道那是越来越多&#xff0c;企业纷纷在电商平台…

Vue2+OpenLayers添加缩放、滑块缩放、拾取坐标、鹰眼、全屏控件(提供Gitee源码)

目录 一、案例截图 二、安装OpenLayers库 三、代码实现 四、Gitee源码 一、案例截图 二、安装OpenLayers库 npm install ol 三、代码实现 废话不多说&#xff0c;直接给完整代码&#xff0c;替换成自己的KEY即可运行&#xff1a; <template><div><div i…

Vulnhub-Tr0ll靶机笔记

Tr0ll靶机笔记 概述 靶机地址&#xff1a;https://www.vulnhub.com/entry/tr0ll-1,100/ 这台靶机比较简单&#xff0c;包含ftp的渗透&#xff0c;pcap流量包的分析&#xff0c;常规的web渗透和系统内核提权。让我们开始吧 Hack it&#xff01; 一、nmap扫描 1、端口扫描 …

高效建站指南:通过Portainer快速搭建自己的在线网站

文章目录 前言1. 安装Portainer1.1 访问Portainer Web界面 2. 使用Portainer创建Nginx容器3. 将Web静态站点实现公网访问4. 配置Web站点公网访问地址4.1公网访问Web站点 5. 固定Web静态站点公网地址6. 固定公网地址访问Web静态站点 前言 Portainer是一个开源的Docker轻量级可视…

Docker Compose的使用

文章首发于我的博客&#xff1a;https://blog.liuzijian.com/post/docker-compose.html 目录 Docker Compose是什么Docker Compose安装Docker Compose文件Docker Compose常用命令案例&#xff1a;部署WordPress博客系统 Docker Compose是什么 Docker Compose是Docker官方的开源…

JDK长期支持版本(LTS)

https://blogs.oracle.com/java/post/the-arrival-of-java-23 jdk长期支持版本&#xff08;LTS&#xff09;&#xff1a;JDK 8、11、17、21&#xff1a;