【算法杂货铺】二分算法


目录

🌈前言🌈

📁 朴素二分查找

 📂 朴素二分模板

📁 查找区间端点处

细节(重要)

 📂 区间左端点处模板

 📂 区间右端点处模板

📁 习题

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

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

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

4. LCR 173. 点名 - 力扣(LeetCode)        

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

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

📁 总结

🌈前言🌈

        欢迎观看本期【算法杂货铺】,本期内容将讲解基础算法中的——二分算法。二分查找算法,就是利用二段性,将问题划分成两个区间,去掉一个区间,在另一个区间查找答案。

        二分查找是有模板供大家使用的,即背下模板,只需要略微修改即可,但本文旨在理解这些模板。

        本篇文章通过讲解例题,带大家快速了解二分算法,分为3步,讲解各个二分算法。1. 题目解析;2. 算法思路;3. 代码展示。

📁 朴素二分查找

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

1. 题目解析

        相信有一点语言基础的人都会这道题目,我们只需要遍历一遍这个数组即可,如果找到范围下标,没有找到返回-1,但是这种暴力解法,时间复杂度是O(N)。

        这里我们还有一个初始条件没有用到,即n个元素使有序的(升序)。

2. 算法原理

        上图中,我们通过在一个数组中查找7,给大家展示了二分算法的基本思路。即根据某种规则,将区间划分成两端,再根据规则,舍去一端区间,在另一端区间内查找。

结束条件:

        当 left > right 的时候,不存在区间,循环结束。为什么left可以等于right呢,当只有1个元素的时候,也是要查找的 , 即 (left + right )/2 = right = left 的。

        

为什么是正确的:

        二分算法是利用二段行进行查找的,这道题的二段性体现在数组是有序的。在暴力算法中,我们是一个个比较的,在二分算法中,我们是按区间比较的,即[ left , mid-1 ] 和 [ mid+1 , right]。

时间复杂度:

        我们只需要看循环了多少次即可,只要left <= right的时候,就进循环,当问题规模n就除2。

          当left == right的时候,数组中只剩下一个元素,所以执行x次后,问题规模就为1。​​​​​​​

3. 代码展示

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

 📂 朴素二分模板

while(left <= right)
{//优化:防止溢出int mid = left + (right - left)/2if(...)left = mid + 1;else if(...)right = mid - 1;elsereturn mid;
}

        mid = left + (right - left) /2 是进行了优化的,因为当left 和 right都很大时,很容易超出int类型的范围。mid = left + (right - left + 1) / 2在朴素二分模板中也是可以的。

        +1的区别在于,偶数个数时,是向上取整,还是向下取整。

向下取整:left + ( right - left ) / 2

向下取整:left + (right - left + 1) / 2

📁 查找区间端点处

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

1. 题目解析

        依旧先从简单看起,暴力解法就是先从前往后遍历,找到第一个位置,如果找到,接着从后往前遍历,查找最后一个位置,范围begin和end。否则返回-1,-1。

        暴力解法的时间复杂度是O(N)的,但题目要求我们在O(logN)的时间复杂度。在基础算法中,很少有算法能优化的logN,二分算法就是其中之一。

        这道题目也是一道非常经典的二分查找算法,即查找区间的左端点,和区间的右端点。

2. 算法原理

        用的还是二分算法,即二段行,根据数据的性质,在某种判断条件下将区间一分为二(不一定是有序,有二段性即可。)舍去一个区间,在另一个区间内查找。

寻找左端点思路:

        左区间:[ left , result - 1 ] 都是严格小于 t 的。

        右区间: [ result , right ] 都是严格大于等于 t 的。

​​​​​​​

        因此,关于mid的落点,我们可以分为下面这两种情况:

        1. 当 mid 落在[ left , result - 1 ] 区间的时候,即 nums[mid] < t 。说明 [left , mid ]都是可以舍去的,此时更新left到mid+1的位置,继续在 [ mid +1 , right] 上寻找左边界。

        2. 当mid 落在[result , right]区间的时候,即 nums[mid] >= t。说明[mid +1 ,right]的区间可以社区(mid可能是最终结果,也可能不是),继续在[left,mid]上查找左端点。

注意的是,寻找左端点时,采用向下取整

        左指针的变化:left = mid + 1,区间逐渐减少。

        右指针的变化:right = mid ,可能会原地踏步。如果采用向上取整,可能会死循环,例如:t = 2 , mid = left + (right - left + 1)/2的话,right = mid,mid = right陷入死循环。

//查找区间左端点
int left =0;
int right = nums.size()-1;
while(left < right)
{int mid = left + (right - left)/2;if(nums[mid] >= target){right = mid;}else{left = mid + 1;}
}

 寻找右端点思路:

       左区间:[ left , result ] 都是严格小于等与t的。

       右区间:[ result+1 , right ] 都是严格大于t的。

        1. 当mid落在左区间时,mid可能是最终结果,也可能不是,所以 left = mid,舍去区间[left,mid -1]。继续在区间[ mid , right]区间内查找。

        2. 当mid落在右区间时,可以舍去[mid , right]的区间,right = mid - 1。继续在区间[ left , mid -1 ]内查找。

注意的是,寻找右端点时,采用向上取整

        右指针的变化:right = mid - 1,区间逐渐减少。

        左指针的变化:left = mid,可能会原地踏步,采用向下取整,可能会陷入死循环。例如:t =1 ,mid = left + (right - left) / 2

//查找区间右端点
left = 0;
right = nums.size()-1;
while(left < right)
{int mid = left +(right-left+1)/2;if(nums[mid] <= target){left = mid;}else{right = mid -1;}
}

细节(重要)

 (1) 循环条件 

                left < right。我们以寻找右端点为例:

        left的工作区间就是在小于等于t这个区间;right的工作区间就是在大于t这个区间。因为right = mid - 1,所以right跳出工作区间时,一定是在right == left的位置。

        所以不需要判断right是否等于left,是一定等于的。

​​​​​​​

(2) 求中点操作  

        其实,我们只需要记住,下面有-1的时候,上面就有+1,。其他的结合题意给出二段行的判断条件。

3. 代码展示

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

 📂 区间左端点处模板

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

 📂 区间右端点处模板

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

        以上,就将二分算法的所有知识点讲解完毕,其中朴素二分模板是容易理解的,后面两个二分模板还是需要大家自己画图不断加强理解的。

        下面,我们通过几道习题加强理解。对于习题,不会再过多讲解知识点内容,而是直接给出题解思路,并配图加强理解和做题能力。

📁 习题

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

        我们通过画图,可以看出来,这是一道非常经典的查找区间左端点的题,找到大于等于t的第一个点即可。        

        如果t不存在在数组中,则返回left+1(left 经过循环来到right这个位置,即最后一个位置)

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

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

        其实这道题,你用二分查找左端点,或者查找右端点都可以做出来。这里我们就以查找右端点为例,当然,也会展示左端点的解法。

        前面需要特殊判断x是否小于1,如果小于1,则返回0。

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

        以上是查找右端点的写法,当然也可以写成查找左端点。

        不过因为,查找左端点,数值是会比查找右端点大的多,所以,left 和 right都采用 long long类型。最后判断一下,right的平方是否等于x,如果不等于返回right-1。

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

        这道题,可以很好的理解二分法,即一道题往往不止一种解决方法。

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

        通过题意,可以了解的是,旋转就是将最后一个元素移动到最开始的位置。此外,还有一个重要条件就是 互不相同

        那我们就可以利用互不相同,得出二段行,如下图所示。

         因此,我么以D点的值为索引,

        当mid落在区间[A,B]时,left = mid + 1。[A,B]区间内的点是严格大于D点的值。

        当mid落在区间[C,D]时,right = mid。其中C点就是我们要求的点。C点的值是要个小于D点的值。当[C,D]区间内只有一个元素时,C点的值可能等于D点的值。

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

4. LCR 173. 点名 - 力扣(LeetCode)        

        ​​​​​​​数组中,下标等于元素值,如果有一位同学缺席,那么它之后所有元素的下标就不等于元素的值。

        因此,我们只需要查找左端点即可。

        此外,这道题还需要注意的是,最后需要处理一下边界情况,如数组中仅有一个元素1,那么缺席的同学编号就是0;如果仅有一个元素0,那么缺席的同学编号为1。

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

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

        这道题目,查找左端点或查找右端点都可以,这里我们就以查找右端点为例,不在讲解如何查找左端点,感兴趣可以自己实践一下。

​​​​​​​

        当mid处于上升区间时,在[mid + 1, right]区间查找。

        当mid处于下降区间时,在[left , mid - 1]区间查找。

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

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

 寻找⼆段性:

        1. 当arr[mid] > arr[mid+1],左侧一定存在山峰,到[left,mid]寻找山峰。

        2. 当arr[mid] < arr[mid+1],右侧一定存在山峰,到[mid+1,right]寻找。

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

📁 总结

        以上,我们就将二分算法带大家做了全面了解。二分算法的核心就是寻找二段性,有了二段性,直接套模板即可。对于模板,建议是理解着去背诵,效果更好。

        通过习题,我们也加强了对二分算法的理解,平时做题中,看到时间复杂度为logN的,我们应该敏锐的想到二分算法。

        以上就是本期【算法杂货铺】的主要内容了,如果感觉对你有帮助,欢迎点赞,收藏,关注。Thanks♪(・ω・)ノ

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

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

相关文章

phpcms头像上传漏洞引发的故事

目录 关键代码 第一次防御 第一次绕过 第二次防御 第二次绕过 第三次防御 第三次绕过 如何构造一个出错的压缩包 第四次防御 第四次绕过 本篇文章是参考某位大佬与开发人员对于文件包含漏洞的较量记录下的故事&#xff0c;因为要学习文件包含漏洞&#xff0c;就将大佬…

什么是 HTTPS?它是如何解决安全性问题的?

什么是 HTTPS&#xff1f; HTTPS&#xff08;HyperText Transfer Protocol Secure&#xff09;是一种安全的通信协议&#xff0c;用于在计算机网络上安全地传输超文本&#xff08;如网页、图像、视频等&#xff09;和其他数据。它是 HTTP 协议的安全版本&#xff0c;通过使用加…

Java开发从入门到精通(九):Java的面向对象OOP:成员变量、成员方法、类变量、类方法、代码块、单例设计模式

Java大数据开发和安全开发 &#xff08;一)Java的变量和方法1.1 成员变量1.2 成员方法1.3 static关键字1.3.1 static修饰成员变量1.3.1 static修饰成员变量的应用场景1.3.1 static修饰成员方法1.3.1 static修饰成员方法的应用场景1.3.1 static的注意事项1.3.1 static的应用知识…

微服务技术栈SpringCloud+RabbitMQ+Docker+Redis+搜索+分布式(五):分布式搜索 ES-中

文章目录 一、DSL查询文档1.1 简单查询1.2 复合查询 二、搜索结果处理三、RestClient演示 查询与结果分析四、案例4.1 问题解析4.2 代码4.2.1 实体bean4.2.2 控制层4.2.3 业务service4.2.4 启动类 一、DSL查询文档 1.1 简单查询 # 1. DSL查询 # 1.1 查询所有GET /hotel/_searc…

JavaScript 进阶(一)

一、作用域 作用域&#xff08;scope&#xff09;规定了变量能够被访问的“范围”&#xff0c;离开了这个“范围”变量便不能被访问。 作用域分为&#xff1a; 局部作用域 、全局作用域。 1.1局部作用域 局部作用域分为函数作用域和块作用域。 1. 函数作用域&#xff1a; 在函数…

分布式系统常见负载均衡实现模式

分布式系统常见负载均衡实现模式 1. 4层负载均衡1.1. 负载均衡的常见需求1.2. 负载均衡的实现模式1.2.1 DR模式1.2.2 TUN模式1.2.3 NAT模式1.2.4 FULLNAT模式1.2.5 4种模式的差异 1.3. 负载均衡的均衡算法1.3.1 静态负载均衡1.3.2 轮询法1.3.3 加权循环法1.3.4 IP 哈希法1.3.5 …

Vue前端开发记录(一)

本篇文章中的图片均为深色背景&#xff0c;请于深色模式下观看 说明&#xff1a;本篇文章的内容为vue前端的开发记录&#xff0c;作者在这方面的底蕴有限&#xff0c;所以仅作为参考 文章目录 一、安装配置nodejs,vue二、vue项目目录结构三、前期注意事项0、组件1、数不清的报…

首个ChatGPT机器人- Figure 01;李开复旗下零一万物推出Yi系列AI大模型API

&#x1f989; AI新闻 &#x1f680; 首个ChatGPT机器人- Figure 01 摘要&#xff1a;Figure 01是一个由初创公司Figure联合OpenAI开发的人形机器人。它展示了与人类和环境互动的能力&#xff0c;可以说话、看东西&#xff0c;并且可以执行各种任务&#xff0c;如递食物、捡垃…

QT中dumpcpp以及dumpdoc使用

qt中调用COM的方式方法有四种&#xff0c;参考解释在 Qt 中使用 ActiveX 控件和 COM (runebook.dev) 介绍dumpcpp的使用方法Qt - dumpcpp 工具 (ActiveQt) (runebook.dev)&#xff1a; 在安装好了的qt电脑上&#xff0c;通过powershell窗口来实现&#xff0c;powershell比cmd要…

算法的基本概念和复杂度

目录 一. 算法的基本概念1.1 什么是算法1.2 算法的五个特性1.3 怎么才算好的算法 二. 算法的时间复杂度三. 算法的空间复杂度 \quad 一. 算法的基本概念 \quad \quad 1.1 什么是算法 算法可以用自然语言来描述, 也可以用伪代码和代码来描述 \quad 1.2 算法的五个特性 有穷性, 一…

ASP.NET-Server.HtmlEncode

目录 背景: 1.转义特殊字符&#xff1a; 2.防止跨站脚本攻击&#xff08;XSS&#xff09;&#xff1a; 3.确保输出安全性&#xff1a; 4.保留原始文本形式&#xff1a; 5.与用户输入交互安全&#xff1a; 实例说明: 不用Server.HtmlEncode 效果展示: 用Server.HtmlEnc…

SpringMVC重点记录

目录 1.学习重点2.回顾MVC3.回顾servlet4.初始SpringMVC4.1.为什么要学SpringMVC?4.2.SpringMVC的中重点DispatcherServlet4.3.SpringMVC项目的搭建4.4.MVC框架要做哪些事情?4.5.可能会遇到的问题 5.SpringMVC的执行原理6.使用注解开发SpringMVC7.Controller控制总结8.RestF…

VSCode ARM CortexM 开发

VSCode ARM CortexM 开发: http://coffeelatte.vip.cpolar.top/post/software/applications/vscode/vscode_arm_cortexm_开发/ 文章目录 VSCode ARM CortexM 开发: <http://coffeelatte.vip.cpolar.top/post/software/applications/vscode/vscode_arm_cortexm_%E5%BC%80%E5%…

鸿蒙实战开发:【分布式软总线组件】

简介 现实中多设备间通信方式多种多样(WIFI、蓝牙等)&#xff0c;不同的通信方式使用差异大&#xff0c;导致通信问题多&#xff1b;同时还面临设备间通信链路的融合共享和冲突无法处理等挑战。分布式软总线实现近场设备间统一的分布式通信管理能力&#xff0c;提供不区分链路…

音频占用磁盘空间太多 需要把mp3音频转aac音频缩小占用空间 应该怎么操作?

一&#xff1a;什么是aac格式&#xff1f; aac是一种音频压缩格式&#xff0c;它是MPEG-2标准下的一种音频压缩方式&#xff0c;也可以作为HE-AAC&#xff0c;AAC或AAC-LC格式使用&#xff0c;是音频压缩领域中的一种重要格式。与MP3的比较&#xff0c;aac在保证音质的同时可以…

K8S CNI

OCI概念 OCI&#xff0c;Open Container Initiative&#xff0c;开放容器标准&#xff0c;是一个轻量级&#xff0c;开放的治理结构&#xff08;项目&#xff09;&#xff0c;在 Linux 基金会的支持下成立&#xff0c;致力于围绕容器格式和运行时创建开放的行业标准。 OCI 项目…

stm32-定时器输入捕获

目录 一、输入捕获简介 二、输入捕获框图 1.定时器总框图 2.输入捕获框图 3.主从触发模式 三、固件库实现 1.定时器测量PWM频率 2.PWMI模式 一、输入捕获简介 二、输入捕获框图 1.定时器总框图 上图可知&#xff0c;四个输入捕获和输出比较共用4个CCR寄存器&#x…

Android SystemServer进程解析

SystemServer进程在android系统中占了举足轻重的地位&#xff0c;系统的所有服务和SystemUI都是由它启动。 一、SystemServer进程主函数流程 1、主函数三部曲 //frameworks/base/services/java/com/android/server/SystemServer.java /** * The main entry point from zy…

人工智能程序使用的编程语言

用C语言可以写人工智能程序吗&#xff1f; 可以用C语言编写具有人工智能功能的程序&#xff0c;但是较为复杂。C语言是一种通用的编程语言&#xff0c;它在执行速度和资源控制方面表现出色&#xff0c;这使得它适合于需要高性能处理的人工智能应用&#xff0c;如游戏AI&#xf…

邮件安全|“AI钓鱼邮件”愈发泛滥,钓鱼邮件如何防“钓”?

毋庸置疑&#xff0c;人工智能是把双刃剑。 在AI蓬勃发展的过程中&#xff0c;潜在的风险正在悄然滋长。 近日&#xff0c;网络安全公司Enea发布的一份报告指出&#xff0c;随着以人工智能驱动的语音钓鱼&#xff08;vishing&#xff09;和短信钓鱼&#xff08;smishing&…