【二分查找】--- 二分模板总结

 Welcome to 9ilk's Code World

       

(๑•́ ₃ •̀๑) 个人主页:        9ilk

(๑•́ ₃ •̀๑) 文章专栏:     算法Journey


从本博客开始,博主将开始分享二分查找算法的相关知识。


🏠 朴素二分模板 ---  二分查找

📌 题目内容

二分查找

📌 题目解析

  本题是比较简单的二分查找,其中题中数组是有序的且元素不重复。

📌算法原理

✏️ 思路一:暴力解法

暴力解法思路很简单,其实就是遍历一遍数组找到target则退出,时间复杂度:O(N)

✏️ 思路二:二分查找

   在暴力解法的基础上,我们发现,当我们找到target的时候,由于整个数组有序,target能把整个数组分成两段,一段是小于target的,另一段是大于target的,由此我们说数组具有“二段性”。

何为二段性

当题目中能发现一个规律,根据这个规律选取某个区间或一个点,使得数组能够划分为两段区间,然后能舍弃一部分,这就是所谓的“二段性”。当我们发现具有二段性时,就可以使用二分查找。

划分方案的选择:

 既然目的是为了把区间划分为两段,那么其实不同的划分方案也是行的通的。

      

但是数学证明,选取1/2处的划分方案是最快的,因此我们常用的是二分。

朴素二分核心:

  • 当arr[mid] < target ,由于数组有序说明[left,target]区间的值都小于target,要向右查找
  • 当arr[mid] > target, 由于数组有序说明[mid,right]区间的值都大于target,要向左查找
  • 当arr[mid] == target时,此时找到返回结果。
细节问题:
  •  循环结束条件:

当区间不断缩小到只有一个数时,也就是left == right时,由于在这个过程中我们虽然能肉眼看出这是我们要的结果,但是程序仍然是要进入循环才能做出判断的,因此left <= right时我们进行循环查找。

  • 为什么二分查找是正确的

我们之前用暴力查找是正确的,这是毋庸置疑的,而我们的二分查找是利用了数组有序这个特性干着暴力查找的事,只不过我们不是一个一个查找,而是进行有效筛选后查找。

  • 二分查找的时间复杂度

1次折半缩小到n/2范围

2次折半缩小到n/4范围

3次折半缩小到n/8范围

....

x次折半缩小到1个数

==>n  = 2的x次方 ==> x = logn

==>时间复杂度为O(logn),查找了logn次。

  • 两种不同的划分中点方式
//1 int mid = (left + right) / 2;
//2 int mid = left + (right - left) / 2;
//3 int mid = left + (right - left + 1) / 2;

对于第一种方式我们并不建议,如果两个数都很接近整形最大,此时相加可能会发生溢出。

对于第二种和第三种方式,如果数据个数是偶数个他们选取到的中点有所不同,但我们的目的是找到点划分范围,因此并不影响选哪种。

参考代码:

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

📌 总结朴素二分算法模板

while(left <= right)
{int mid = left + (right - left) / 2;//也可以是left+(right-left+1)/2;if(...) left = mid + 1;else if(...) right = mid -1; else return ...
}

注:...表示具体问题具体分析。

🏠 查找左边界及右边界的二分模板

📌题目内容

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

📌题目解析

  • 题目中的数组是非递减的。
  • 题目中的数组存在重复数字。

📌 算法原理

✏️ 思路一:朴素二分再分类讨论

  我们朴素二分可以找到一个目标值,但题目要求我们找到两个相同的目标值,一个是第一次出现,另一个是第二次出现,此时我们可以先用二分不管在哪个位置找出一个再分类讨论位置找两个端点:

  • target只出现一次,直接返回当前位置
  • 找到的target是连续target中的左端点。
  • 找到的target是连续target中的右端点
  • 找到的target是连续target中的中间位置。

参考代码

class Solution 
{
public:
typedef long long ll;vector<int> searchRange(vector<int>& nums, int target){vector<int> del = {-1,-1};if(nums.size() == 0){return del;}int left = 0;int right = nums.size()-1;vector<int> v;bool flag = false;int mid = 0;while(left <= right){mid = left + (right-left) / 2;if(nums[mid] < target){left = mid + 1;}else if(nums[mid] > target){right = mid - 1;}else{flag = true;break;}}if(!flag)return del; //表示没找到if(mid-1 > 0 && mid+1 <nums.size() &&nums[mid-1] != target && nums[mid+1] != target){return vector<int>({mid,mid});}else if((mid-1 > 0 && mid+1 <nums.size()  && nums[mid-1] != target && nums[mid+1] == target) || mid == 0){int cur = mid+1;while(cur < nums.size()){if(nums[cur] == target)cur++;elsebreak; }return vector<int>({mid,cur-1});}else if((mid-1 > 0 && mid+1 <nums.size()  && nums[mid-1]== target && nums[mid+1] != target) || mid == nums.size() -1){int cur = mid-1;while(cur >= 0){if(nums[cur] == target)cur--;elsebreak; }return vector<int>({cur+1,mid});}else{int cur1 = mid+1;int cur2 = mid-1;while(cur1 < nums.size()){if(nums[cur1] == target)cur1++;elsebreak;    }   while(cur2 >=0){if(nums[cur2] == target)cur2--;elsebreak;}return vector<int>({cur2+1,cur1-1});}}};

代码又臭又长,有没有办法先找出左端点再找出右端点呢?

✏️ 思路二:边界二分

📒 区间左端点

  • 找区间左端点时我们仍可以利用二段性,我们发现左端点把整个数组划分为左边部分小于target,右边部分大于等于target.
  • 当mid处的值小于target时,此时说明在小于target的区间内,我们需要向右寻找,因此是left = mid +1.
  • 当mid处的值大于等于target时,此时说明在大于等于target的区间内,此时要找左端点我们需要向左边寻找,向左缩小范围,又由于x可能正好就是左端点,因此更新right时只能将right更新到mid的位置,如果是right = mid-1,[left,right]就没有左端点了,毕竟[mid,right]区间内的值都是大于等于target的。
细节处理:
  • 循环条件

对于【left,right】区间内值的情况我们可以分下列三种情况:

1. 对于第一种情况,[left,right]区间内有我们要找的左端点,此时left每一次移动是为了跳出小于target的区间,而right每一次移动是为了逼近左端点,因此最终left == right时就是我们要找的左端点。

2.对于第二种情况,如果区间全是target的,此时right会一直向左逼近,因为mid都是大于target的,从而最后到达left的位置,如果是left <= right的话,right到达left的位置时,mid一直是left的位置从而导致死循环。

3.第三种情况类似第二种,left一直逼近right直到到达right的位置,如果判断条件是left<=right的话也会导致死循环。

因此得出:

  1. left == right时就是最终结果无需判断。
  2. 如果判断就会导致死循环。
  • 求中点的操作

我们前面说明了求中点防溢出有两种求法

1.当采取第一种方式求中点时,此时mid求到的是left位置,由于left位置是小于target的,因此此时mid位置是小于target的,left会移动right处(mid+1)从而退出循环。

2.当采用第二种方式求中点时,此时mid求到的是right位置 ,由于right位置是大于等于target的,因此此时会一直right = mid陷入死循环。

因此得出:求左端点时,我们采用left + (right - left)/2的方式。

📒 区间右端点

  • 找区间右端点时我们仍可以利用二段性,我们发现右端点把整个数组划分为左边部分小于等于target,右边部分大于target.
  • 当mid处的值大于target时,此时说明在大于target的区间内,我们需要向左寻找,因此是right= mid - 1.
  • 当mid处的值小于等于target时,此时说明在小于等于target的区间内,此时要找右端点我们需要向右边寻找,向右缩小范围,又由于x可能正好就是右端点,因此更新left时只能将left更新到mid的位置,如果是left = mid+1,[left,right]就没有左端点了毕竟[left,mid]区间内的值都是小于等于target的。
细节处理
  • 循环条件

同分析左端点一样,循环条件也需要是left < right否则陷入死循环。

  • 求中点

1.当采取第一种方式求中点时,此时mid求到的是left位置,由于left位置是小于target的,因此此时mid位置是小于target的,left会一直left=mid陷入死循环

2.当采用第二种方式求中点时,此时mid求到的是right位置 ,由于right位置是大于target的,此时mid位置大于target,right会移动到left位置从而退出循环.

因此得出:求区间右端点时,采用left +(right-left+1)/2的方式。

因此对于本道题我们可以分别用这两种方法求出左右边界,参考代码如下:

class Solution 
{
public:
typedef long long ll;vector<int> searchRange(vector<int>& nums, int target){vector<int> del = { -1,-1 };if (nums.size() == 0){return del;}//求左端点int left = 0;int right = nums.size() - 1;int leftmid = 0;while (left < right){leftmid = left + (right - left) / 2;if (nums[leftmid] < target){left = leftmid + 1;}else{right = leftmid;}}if (nums[left] != target)leftmid = -1;elseleftmid = left;//求右端点left = 0;right = nums.size() - 1;int rightmid = 0;while (left < right){rightmid = left + (right - left + 1) / 2;if (nums[rightmid] > target){right = rightmid - 1;cout << right << " " << endl;}else{left = rightmid;}}if (nums[left] != target)rightmid = -1;elserightmid = left;return vector<int>({ leftmid,rightmid });}};

时间复杂度:O(logN)

📌 总结查找左边界及右边界二分模板

查找左边界:

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

查找右边界:

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

总结:

本博客我们讲解了朴素二分模板以及边界二分模板,朴素二分模板应用比较局限,而对于边界二分模板我们更常用.对于边界二分模板,我们要处理好它的循环条件以及求中点,同时根据我们求的左端点还是右端点来更新left和right.

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

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

相关文章

“失业浪潮中的转机:程序员自学AI绘画,打造副业黄金通道“

正文&#xff1a; 一、失业潮下的焦虑 近年来&#xff0c;随着互联网行业的快速发展&#xff0c;程序员这个职业在我国逐渐成为热门。然而&#xff0c;随着市场竞争的加剧&#xff0c;程序员们也开始面临失业的风险。小张&#xff0c;一名90后程序员&#xff0c;就在这波失业潮…

【Unity教程】使用 Animation Rigging实现IK制作程序化的动画

在 Unity 开发中&#xff0c;为角色创建逼真且自适应的动画是提升游戏体验的关键。在本教程中&#xff0c;我们将结合 Animation Rigging 工具和 IK&#xff08;Inverse Kinematics&#xff0c;反向运动学&#xff09;插件来实现程序化的动画。 视频教程可以参考b战大佬的视频 …

notepad++安装HexEdit插件

notepad安装HexEdit插件 打开notepad&#xff0c;选择插件—>插件管理 在这里找到HexEdit点击安装就可以 点击完&#xff0c;notepad会自动重启&#xff0c;重启完成就安装好了

(24)(24.3) MSP OSD(二)

文章目录 前言 3 OSD面板项目配置 4 使用SITL测试OSD 5 使用任务规划器配置布局 6 视频 前言 ArduPilot 支持 MSP OSD 协议&#xff0c;该协议允许在 DJI 护目镜上显示飞行数据&#xff0c;就像许多自动驾驶仪中的外部 MAVLink OSD 或内部集成模拟 OSD 一样。如果配置了 …

打靶记录11——Billu_b0x

靶机&#xff1a; https://download.vulnhub.com/billu/Billu_b0x.zip难度&#xff1a; 中&#xff08;两种攻击路线&#xff09; 目标&#xff1a; 取得root权限 涉及的攻击方法&#xff1a; 主机发现端口扫描Web信息收集SQL注入&#xff08;Sqlmap跑不出来&#xff09;…

最新的APS高级计划排程系统推动的MRP供应链计划是什么?

在当下“内卷”的市场环境下&#xff0c;制造业的订单需求从过去大批量标准品生产已经演变成小批量、多订单的非标订单生产&#xff0c;这对制造业的供应链提出了更高的要求。为了应对市场实现产销平衡&#xff0c;中大型的企业都开始重视供应链的建设工作&#xff0c;以应对企…

win10配置pytorch环境+CUDA安装

步骤 1&#xff1a;更新显卡驱动 参考&#xff1a;如何在windows上 安装&更新 显卡的驱动_显卡驱动series和notebook-CSDN博客 进入英伟达官网&#xff1a;下载 NVIDIA 官方驱动 | NVIDIA 根据GPU类型选择对应的NVIDIA驱动&#xff0c;选好后点击“查找” 选择下载 GeFo…

记录|C#中panel与panel重叠显示问题

目录 前言一、问题在现二、方案解决三、效果展示更新时间 前言 参考文章&#xff1a; C#中winform中panel重叠无法显示问题的解决 一、问题在现 问题是我实现上图中效果&#xff0c;但是panel和panel的交界处放入其他组件后&#xff0c;会被部分覆盖【如下图示】 二、方案解决…

UniApp的神器-开启前端开发的全新篇章

本文介绍了DIYGW UniApp可视化工具作为一款低代码开发平台的特点和优势。该工具采用拖拽式设计和模块化开发&#xff0c;能够快速转化想法为可运行应用&#xff0c;并支持多种平台部署。它具有所见即所得的设计体验、丰富的组件库、前后台通信模块和跨平台兼容性等特点。使用该…

Astro + Cloudflare Pages 快速搭建个人博客

目录 1 选择 Astro 模板2 使用代码3 修改代码4 上传 Github5 部署 Cloudflare Pages6 后续修改 最近我搭建完了我的个人网站&#xff0c;很多人问是怎么做的&#xff0c;今天就来写一篇教程吧。 全部干货&#xff0c;看完绝对能成功搭建自己的网站&#xff01;&#xff08;还不…

服装行业的利器:RFID智能吊挂分拣系统

服装行业的利器&#xff1a;RFID智能吊挂分拣系统 服装业继续走粗放型老路的利润空间越来越小&#xff0c;行业内过度竞争利润降低&#xff0c;原料价格上涨导致成本上升。企业内部生产技术创新不足、工厂生产效率低&#xff0c;导致产出不够、货期竞争乏力。企业为了盈利生存…

【乐吾乐大屏可视化组态编辑器】动态图表

动态图表 在线使用&#xff1a;https://v.le5le.com/ 1. 建立数据列表 左侧选择数据栏&#xff0c;列表栏建立数据&#xff08;变量&#xff09;列表。具体查看&#xff1a; 数据绑定 2.绑定数据点 官方图表默认都开启了模拟数据&#xff0c;可以在数据-列表中取消“开启全…

SDL 锁屏视频卡死bug原因

最近在封装播放库&#xff0c;我用的是FFMPEGSDL库封装&#xff0c;这个库其实用起来不难&#xff0c;因为网上可供参考的资源也多&#xff0c;所以我自己也封装了一个&#xff0c;但是播放视频时只要我电脑一锁屏再重新打开&#xff0c;我靠视频卡住不动了&#xff0c;我调试看…

gitlab自动部署是什么 gitlab自动部署如何进行操作

在现代软件开发流程中&#xff0c;自动化部署是提高效率和确保软件质量的关键环节。GitLab作为一个强大的DevOps平台&#xff0c;提供了完整的自动部署工具&#xff0c;帮助开发团队实现代码从编写到生产的无缝转换。本文将详细解析GitLab的自动部署功能是什么&#xff0c;如何…

C语言典型例题37

《C程序设计教程&#xff08;第四版&#xff09;——谭浩强》 例题3.5 按照按照考试成绩的等级输出百分制分数段&#xff0c;A等为85分以上&#xff0c;B等为70~84分&#xff0c;C等为 60~69分&#xff0c;D等在60分以下&#xff0c;成绩的等级从键盘输入 代码&#xff1a; //…

搜维尔科技:Varjo XR-4 功能详解:实现业界首个凝视驱动自动对焦系统

对可变焦光学元件的需求 目前&#xff0c;所有其他XR HMD都在视频直通摄像头中使用定焦光学元件&#xff0c;其焦距无法改变。人眼可以辨别高达约 60 像素/度 ( PPD ) 的细节&#xff0c;但定焦光学元件的问题在于&#xff0c;在实践中&#xff0c;它们的分辨率极限约为 30 PP…

vulnhub靶机 DC-9(渗透测试详解)

一、靶机信息收集 1、靶机下载 https://download.vulnhub.com/dc/DC-9.zip 2、靶机IP扫描 3、探测靶机主机、端口、服务版本信息 4、靶机目录扫描 二、web渗透测试 1、访问靶机IP 查看页面功能点&#xff0c;发现一个搜索框和登录框 2、测试一下是否存在sql注入 查看当前数…

激光雷达点云投影到图像平面

将激光雷达点云投影到图像平面涉及几何变换和相机模型的应用。以下是该过程的基本原理&#xff1a; 1. 坐标系转换 激光雷达生成的点云通常位于激光雷达的坐标系中&#xff0c;而图像则在相机坐标系中。为了将点云投影到图像上&#xff0c;首先需要将点云从激光雷达坐标系转换…

GitHub Actions 遭利用,14个热门开源项目令牌泄露风险激增

近日&#xff0c;有攻击者通过 CI/CD 工作流中的 GitHub Actions 工具窃取了谷歌、微软、AWS 和 Red Hat 等多个知名开源项目的 GitHub 身份验证令牌。 窃取这些令牌的攻击者可在未经授权的情况下访问私有存储库、窃取源代码或向项目中注入恶意代码。 Palo Alto Networks Un…

docker部署redis

1.搜索镜像 docker search redis 2.拉取镜像 可省略第二步&#xff0c;直接执行第三步 docker pull redis 3.创建Redis容器并设置密码 也可以不设置密码 不设置密码&#xff1a; docker run -d -p 6379:6379 \ -v /Users/hal/DevelopmentToolkit/redis/redis.conf:/etc/red…