专题三_二分查找算法_算法详细总结

目录

二分查找

1.⼆分查找(easy)

1)朴素二分查找,就是设mid=(left+right)/2,x=nums[mid],t就是我们要找的值

2)二分查找就是要求保证数组有序的前提下才能进行。

3)细节问题:

总结:

细节:

2.二分查找的进阶:

查找区间左端点:

查找区间右端点:

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

解析:

总结:

总结二分模板:

记忆:

4.搜索插⼊位置(easy)

解析:

5.x的平方根(easy)

解析:

1)暴力:

2)优化:

6.⼭峰数组的峰顶(easy)

解析:

总结:

7.寻找峰值(medium)

解析:

8.搜索旋转排序数组中的最⼩值(medium)

解析:

1)优化:

以D为target:

以A为target:

总结:

9.LCR 点名

解法一:

解法二:

总结一下:


二分查找

二分查找算法,当我们发现一个规律,能在这个规律里面选取一个点之后,能把数组分成两部分,有选择性的选择一部分,进而能在另一部分继续选择,就说明数组有“二段性”。不一定非要数组完全有序,只要能找到一部分有规律,就可以采用二分查找。我们可以找这个数组的二分之一、三分之一、四分之一都行,因为只需要我们能将数组分成两部分就ok。

只要弄清除二分查找算法的进阶,查找"数组"的最左端点 或 查找"数组"的最右端点,对于二分查找的问题一般就迎刃而解了。

那么我们先来看一下朴素的二分查找,后面就来进阶:

1.⼆分查找(easy)

这题就是朴素的二分查找,简直入门级别。了解什么是二分,在什么情况下使用二分。

解析:

1)朴素二分查找,就是设mid=(left+right)/2,x=nums[mid],t就是我们要找的值

1.x<t ,lid-1   ->[left,right];
3.x==t   返回结果

2)二分查找就是要求保证数组有序的前提下才能进行。

3)细节问题:

1.循环结束的条件:left>right,那么while(left<=right)
2.为什么是正确的?
是从暴力解法优化过来的
3.时间复杂度,O(logN)

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

总结:

二分查找经典题目。首先最重要的就是判断循环的结束条件,left>right,因为在这之前,left==right的时候,仍然要判断一次nums[left]是否等于target,所以循环条件就是while(left<=right) 然后就是固定的套路

朴素二分查找,就是设mid=(left+right)/2,x=nums[mid],t就是我们要找的值
1.x<t ,left=mid+1   ->[left,right];
2.x>t, right=mid-1   ->[left,right];
3.x==t   返回结果

细节:

为了防止right 和 left 都太大,防止left+right 会存在溢出的风险,所以最好就不要采用这种直接相加的方式。 我们可以先求出这个数组的一半,然后再用这一半长度加上left起始位置,就同样可以得到(left+right)/2 的效果 即:mid=left+(left+right)/2

总结:

二分查找的模板:

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

根据题目要求,往里面填入相关的条件,最主要的判断条件就是left<=right;二段性。

2.二分查找的进阶:

面对二分查找,那么数组是必须要有序才能进行吗?难道数组不有序,就不能进行二分查找了嘛?

面对这些问题,就有了二分的进阶,可以说学会二分进阶,简直无敌~

我们先来上一个简单示例:

eg:

要我们查早数组某一重复元素的开始和结尾的下标,任何利用O(logN) 的时间复杂度进行查找。

第一肯定会想到暴力,但时间复杂度是O(N) 太大,那么任何利用二分,就可以完美解决这个问题。

所以就想到利用二分的性质,将数组分两块!!!二段性~

一块 < t ; 一块 >= t 

那么由此就可以看出,我们要利用二分的性质来查找这一块区间的最左端点和最右端点,就能满足条件,因为这个数组满足二段性,那么如何实现呢?请看下面例子:

查找区间左端点:

1.循环区间:
x<t   ->   left=mid+1   [left,right]
x>=t   ->   right=mid   [left,right]

2.循环条件 left<right  
1).如果left==right  就是最终的结果,无需判断
2).如果判断,那么就会进入死循环,因为mid=(left+right)/2,right=mid,就会一直循环

3.求中点操作
1).left+(right-left)/2, 不能用left+(right-left+1)/2,因为这样的话,如果数组元素个数是偶数个,那么mid就等之间右边的一个元素,这时,right就跳不到left的位置,一直在mid这个位置进行死循环

4.为什么要用right=mid,而不是mid-1;因为我们判断的区间是,在大于等于t这个区间,那么就可能存在等于t的情况,如果冒然写right=mid-1,很可能会跳过nums[right]==t,这个值的区间,所以只能让right=mid

5.为什么判断while(left<=right) 就会死循环?
比如left==right==mid,这三个指针,都指向同一个下标,还要进行判断,那么right就一直等于mid 一直仍然进入循环出不来

查找区间右端点:

1.循环区间:

1)x<=t  那么此时x=nums[mid] 落在区间  [小于等于t] 的位置 那么肯定是更新left=mid,,因为我的left不能越过mid,如果left=mid+1,就会越过mid,如果此时的mid就是指向这个区间的最后一个元素,那么left越过后就到了[mid,right] 这个区间,那么这个区间里面就没有最终的结果

2)x>t 同理就是更新right=mid-1,因为这个区间是确定的,是大于target的值,不会存在我们要找的区间的值,就可以越过mid,成为mid-1

2.循环条件:
left<right,因为跟求左端点一样,如果判断等于的话,left,right,mid三个指针在同一个位置,会陷入死循环

3.求中点的方式

1)这次是采用left+(right-left+1)/2,不能采用left+(right-left)/2,因为正好跟求左端点相反,这里求右端点,left==mid,为了防止left一直等于mid陷入死循环,要让mid指向right这一边的中点,所以要让right-left+1

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

经过上面学习到的二分查找数组区间的左右端点,可以直接试试这题,会有很好的效果,是真的简单。

解析:

1)先将数组分块后,对其求mid,然后判断是求左端点还是求右端点

2)确定好后,对于nums[left] < target  ->   left=mid+1 的判断,说明left是可以越过mid的。

对于下面的记忆过程可以确定,上面的mid = left+ (right-left)/2 ,是没有+1的。

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

总结:

1)暴力:
从头开始遍历,那时间复杂度肯定O(N^2) 绝对超时

2)优化,二分查找左右两端点
1.查找左端点
2.查找右端点
模板

总结二分模板:

查找区间左端点:
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(...) right=mid-1;
            else left=mid;
        }
 

记忆:

当上面出现+1的时候,下面就是right=mid-1
当上面没有+1 的时候,下面就是left=mid+1

4.搜索插⼊位置(easy)

这题和前面一样,这个数据具有二段性,如果这时的你能够看出数组具有二段性,你就真的已经成功了一大步。将数组分为一块小于target;一块大于等于target;

解析:

还是一样的,简单的二分查找,对于返回target元素的下标,或者插入的下标,只要在>= target这个区间内,找到最左边的元素就ok,然后<target 区间的元素就已经确定,那么left=mid+1

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

学会了之后真的是很简单这种题目,利用二分效率还很高。

5.x的平方根(easy)

这一题,我第一次做的时候,确实没能看出来二段性,用的暴力,后来才发现原来这样是一样具有二段性,就比如从1~x ,那么一块,i*i<x ; 另一块是 i*i>=x;数组分两块,这样就可以照样用暴力。 

解析:

1)暴力:

求x的平方根,那么就是找i*i<=x 然后返回i,如果暴力,肯定超时,

2)优化:

可以考虑i~x的值,然后对于i*i 是否小于等于x,求这个区间的最右端点,即利用二分查找求最右端点
考虑到[mid,right] 区间是确定大于x的值,那么right=mid-1,那么上面判断mid就是mid=left+(right-left+1)/2,那么left=mid

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

对于这种二段性数组,掌握了套路简直信手拈来。

6.⼭峰数组的峰顶(easy)

题目意思就是说,要求先增后减的数组的峰值,那么这个二段性也太明显了!!!

解析:

1)那么还是简单的二分查找模板,只需要简单判断left 或 right会不会越过峰值即可。

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

总结:

面对求峰值在图中可以看出 left 是可以越过mid的,成为mid+1,所以这下right=mid,

mid=left+(right-left)/2,就全都一气呵成了~

7.寻找峰值(medium)

这题简直根上一题大差不差,只是多了几个峰值而已,但实质上,left和right缩小后其实也就只是在一个峰值范围内。

解析:

1)寻找数组中的任意一个峰值即可,在取mid的过程中,left和right就又被缩小到一个峰值,那么根上一题一样只需要判断left和right会不会越过峰值,带模板就能解决

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

跟上一题一样,就当个练手的,简直easy~

8.搜索旋转排序数组中的最⼩值(medium)

这一题其实二段性超级明显对吧,那么数组分两块,就是根峰值问题不一样的就是数组分两块后全是递增序列,那么我们可以找到里面的规律进行二分。

解析:

1)优化:

这题二分查找有点难度,跟之前的不一样,这个在一个区间内都是单调递增的,所以要单独拿出一个数据当作target,就比如D点,A~B段全是大于D的,C~D全是小于或等于D的。那么求这个数组的最小值,就是求C~D的左端点,那么找最左端点就是二分模板,if(nums[mid]>t) left=mid+1;
            else right=mid;
if(nums[mid]>t) left=mid+1;
            else right=mid;  //nums[mid]<=t,这里是C~D段,为了right不越过最小值点,所以right=mid,left在A~B段进行,所以当mid属于最大值点的时候,left也可以越过变成最小值点 所以left=mid+1

以D为target:

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

以A为target:

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

总结:

这证明了,二分其实是很随意的,但是只要把内在本质掌握好了,不管以哪个点为标准,都可以很快的找到正确答案。

9.LCR 点名

虽然只是一道简单题,但是要是能想到用二分,那么性能又能提升不少~

解法一:

O(N):

1.哈希表

2.直接遍历找结果

3.位运算

4.数学

解法二:

二分查找 

仍然是寻找二段性,然后判断left是否可以越过mid

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

总结一下:

我觉得二分最主要就是刚开始学习的查找左右两端点,如果了解这些了,那么对于二分算法就是了解的透透的了。再就是对于有序数组,第一就应该要想到双指针和二分查找,如果数组分块,具有二段性,那不用说了,妥妥的二分!

我觉得对我的进步挺大的,希望对你也有帮助~

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

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

相关文章

基于SpringBoot+Vue+MySQL的招聘管理系统

系统展示 用户前台界面 管理员后台界面 企业后台界面 系统背景 在当今数字化转型的大潮中&#xff0c;企业对于高效、智能化的人力资源管理系统的需求日益增长。招聘作为人力资源管理的首要环节&#xff0c;其效率与效果直接影响到企业的人才储备与竞争力。因此&#xff0c;构建…

详解Diffusion扩散模型:理论、架构与实现

本文深入探讨了Diffusion扩散模型的概念、架构设计与算法实现&#xff0c;详细解析了模型的前向与逆向过程、编码器与解码器的设计、网络结构与训练过程&#xff0c;结合PyTorch代码示例&#xff0c;提供全面的技术指导。 关注TechLead&#xff0c;复旦AI博士&#xff0c;分享A…

宠物毛发对人体有什么危害?宠物空气净化器小米、希喂、352对比实测

作为一个呼吸科医生&#xff0c;我自己也养猫。软软糯糯的小猫咪谁不爱啊&#xff0c;在养猫的过程中除了欢乐外&#xff0c;也面临着一系列的麻烦&#xff0c;比如要忍耐猫猫拉粑粑臭、掉毛、容易带来细菌等等的问题。然而我发现&#xff0c;现在许多年轻人光顾着养猫快乐了&a…

Linux命令:用于应用补丁文件来更新源代码的工具patch详解

目录 一、概述 二、基本概念 1. 补丁文件 2. diff 工具 三、基本用法 1、基本语法 2、常用选项 3、获取帮助 四、patch 工具的主要功能 1. 应用补丁 2. 逆向应用补丁 3. 查看补丁内容 4. 交互模式 5. 非交互模式 6. 备份文件 五、patch基本用法举例 1、应用补…

动态规划:汉诺塔问题|循环汉诺塔

目录 1. 汉诺塔游戏简介 2.算法原理 3.循环汉诺塔 1. 汉诺塔游戏简介 汉诺塔游戏是一个经典的数学智力游戏&#xff0c;其目标是将塔上不同大小的圆盘全部移动到另一个塔上&#xff0c;且在移动过程中必须遵守以下规则&#xff1a; 每次只能移动一个圆盘较大的圆盘不能放在…

css百分比布局中height:100%不起作用

百分比布局时&#xff0c;我们有时候会遇到给高度 height 设置百分比后无效的情况&#xff0c;而宽度设置百分比却是正常的。 当为一个元素的高度设定为百分比高度时&#xff0c;是相对于父元素的高度来计算的。当没有给父元素设置高度&#xff08;height&#xff09;时或设置…

杂七杂八-系统环境安装

杂七杂八-系统&环境安装 1. 系统安装2. 环境安装 仅个人笔记使用&#xff0c;后续会根据自己遇到问题记录&#xff0c;感谢点赞关注 1. 系统安装 Windows安装linux子系统WSL2&#xff1a;使用windows系统跑linux程序(大模型)WSL VSCode&#xff1a;VSCode连接WSL实现高效…

就服务器而言,ARM架构与X86架构有什么区别?各自的优势在哪里?

一、服务器架构概述 在数字化时代&#xff0c;服务器架构至关重要。服务器是网络核心节点&#xff0c;存储、处理和提供数据与服务&#xff0c;是企业和组织信息化、数字化的关键基础设施。ARM 和 x86 架构为服务器领域两大主要架构&#xff0c;x86 架构服务器在市场占主导&…

学习之git的团队协作

git团队协作 一 团队内协作 生成SSH公钥私钥 一&#xff08;跨团队协作&#xff09;

jmeter之仅一次控制器

仅一次控制器作用&#xff1a; 不管线程组设置多少次循环&#xff0c;它下面的组件都只会执行一次 Tips&#xff1a;很多情况下需要登录才能访问其他接口&#xff0c;比如&#xff1a;商品列表、添加商品到购物车、购物车列表等&#xff0c;在多场景下&#xff0c;登录只需要…

【GBase 8c V5_3.0.0 分布式数据库常用维护命令】

一、查看数据库状态/检查&#xff08;gbase用户&#xff09; 1.gha_ctl monitor 使用gha_ctl monitor查看节点运行情况(跟dcs的地址和端口) gha_ctl monitor -c gbase -l http://172.20.10.8:2379 -Hall |coordinator | datanode | gtm | server|dcs:必选字段。指定查看哪类集…

程序员转行方向推荐

程序员转行方向推荐是一个涉及个人兴趣、技能匹配及市场需求等多方面因素的复杂话题。以下是一些详细的转行方向推荐&#xff0c;旨在帮助程序员在职业生涯中做出更加明智的选择。CSDN大礼包&#xff1a;《2024年最新全套学习资料包》免费分享 技术管理岗位 推荐理由&#xf…

崩坏星穹铁道PC端2.5版本剧情、奖励攻略 用GameViewer远程帮手机减负 随时畅玩星铁PC端

《崩坏&#xff1a;星穹铁道》2.5版本「碧羽飞黄射天狼」在9月10开启&#xff01;上半卡池有五星角色飞霄、知更鸟、卡芙卡、黑天鹅四位角色&#xff0c;还有2.5版本的新剧情&#xff0c;这一次崩铁上线送10连和 1000星琼等其他材料。由于游戏包体过大&#xff0c;不少玩家都选…

光伏开发:工商业光伏的流程管理全面解析

一、项目准备阶段 1、资源寻觅与沟通 首要任务是寻找适合的工商业屋顶或空地资源&#xff0c;并与业主初步交流&#xff0c;了解其意向、屋顶条件及用电情况。这一阶段的关键在于建立信任关系&#xff0c;为后续工作奠定基础。 2、资料收集与核查 全面收集业主资料&#xff…

2.ChatGPT的发展历程:从GPT-1到GPT-4(2/10)

引言 在人工智能领域&#xff0c;自然语言处理&#xff08;NLP&#xff09;是连接人类与机器的重要桥梁。随着技术的不断进步&#xff0c;我们见证了从简单的文本分析到复杂的语言理解的转变。ChatGPT&#xff0c;作为自然语言处理领域的一个里程碑&#xff0c;其发展历程不仅…

2_foc闭环调试_ADC电流采样与滤波及pid数据结构

1、ADC电流采样 上次添加了编码器获取电角度的程序&#xff0c;将之前开环控制的角度进行了替换&#xff0c;这次再将电流采样添加进来&#xff0c;之后就可以利用这样一个有反馈的系统进行电流环PI控制器参数调试。 之前写过ADC&#xff0b;DMA电流采样的stm32库函数程序&…

PPT中的图形与图片:插入、调整与格式设置技术详解

目录 引言 一、图形与图片的插入 1. 插入图形 2. 插入图片 二、图形与图片的调整 1. 调整大小与位置 2. 裁剪与旋转 3. 图形与图片的合并与组合 三、图片格式与布局设置 1. 图片格式设置 2. 图片布局设置 示例案例&#xff1a;制作产品展示PPT 四、结论 引言 在现…

浏览器查消息

window.addEventListener(message,function(event){console.log(Received message,event.data)}); 并把弹窗口对准要接收消息的ifrme 发消息的窗口

20240914 每日AI必读资讯

刚刚&#xff0c;OpenAI震撼发布o1大模型&#xff01;强化学习突破LLM推理极限 - OpenAI o1模型需要简单、直接的提示&#xff0c;而非复杂的指导。 - 避免使用思路链提示&#xff0c;因为o1模型已经具备内部推理能力。 - 使用分隔符来明确模型解析的部分&#xff0c;并限制…

网络编程Udp协议

文章目录 UDP协议1、什么是UDP协议&#xff1f;一、定义与基本概念二、主要特点三、报文格式四、应用场景五、总结 2、如何使用Java中的UDP套接字&#xff1f;一、UDP常用APIDatagramSocketDatagramPacket 二、UDP协议下的客户端-服务器服务器客户端 UDP协议 UDP协议&#xff…