【看海的算法日记✨优选篇✨】第三回:二分之妙,寻径中道

【看海的算法日记✨优选篇✨】第三回:二分之妙,寻径中道

🎬 个人主页:谁在夜里看海.

📖 个人专栏:《C++系列》《Linux系列》《算法系列》

⛰️ 一念既出,万山无阻


目录

📖一、算法思想

细节问题

📚左右临界

📚中点选择 

📚循环条件

📖二、具体运用 

1.⼆分查找

算法思路

算法流程

代码

2.查找元素的第⼀个和最后⼀个位置

算法思路

算法流程

代码

3.x的平⽅根

算法思路

代码

4.⼭峰数组的峰顶

算法思路

算法流程

代码

5.点名

算法思路

代码

📖三、总结


📖一、算法思想

二分算法是一种经典的高效查询方法,它的核心思想是通过不断将查找范围缩小为一半,从而大大减少查找的时间复杂度。

例如,在一个有序数组中,我们要查找指定元素,最简单的方法是遍历数组,时间复杂度为O(n);

然而使用二分算法,cur每次从待遍历数组的中心位置开始,判断元素大小:

① <目标元素,说明目标元素在右区间,更新cur,指向右区间的中心位置;

② >目标元素,说明目标元素在左区间,更新cur,指向左区间的中心位置。

此时最坏情况是遍历log(n)次,因此时间复杂度为log(n),这意味着,在100万个数中查找目标元素最多只需要遍历20次,极大提高了效率。

二分算法的本质思想理解起来并不难,但是在具体运用之前,我们还需要对二分算法有一个更深入的了解:二分算法的细节问题

细节问题

📚左右临界

在二分算法的具体运用中,我们不仅需要一个cur指针,指向区间的中点,还需要left和right指针,标记区间的左右临界位置,每次遍历之后都需要对临界位置进行更新,更新需要分为三种情况:

①:目标元素(不重复)

这种情况是最好处理的,每次更新时直接将左指针(或右指针)指向cur后一个位置(或前一个位置)即可:

②:连续序列的左端点

如果我们要查询的不是一个元素,而是一个连续序列的左端点,例如在 “1, 3, 5, 6, 6, 7, 9, 10” 中查找元素6的开始位置:

这个时候cur等于目标值,但是并不是需要的结果,此时cur应该继续向左区间移动,但是right指针该怎么调整呢?

我们可以把数组看成a、b两个区间,而我们最终要找的是b区间的左端点:

① cur < 目标值,cur需要指向右区间的中点,而左区间(1,2,3)被排除了,所以left指向cur的后一个位置;

② cur >= 目标值,由于我们要找的数连续序列的左端点,所以此时cur需要更新到左区间的中点,而right需指向原cur位置处(当cur=目标值时,cur可能是最终结果也可能不是,所以需要保存当前位置)

③:连续序列的右端点

例如在 “1, 3, 5, 6, 6, 7, 9, 10” 中查找元素6的结束位置:

同样可以看成a、b两个区间,而我们要找的是区间a的右端点:

① cur > 目标值,cur需要指向左区间的中点,而右区间(7,9,10)被排除了,所以right指向cur的前一个位置;

② cur <= 目标值,由于我们要找的数连续序列的右端点,所以此时cur需要更新到右区间的中点,而left需指向原cur位置处(当cur=目标值时,cur可能是最终结果也可能不是,所以需要保存当前位置)
📚中点选择 

在实际运用中,我们会发现,如果序列是偶数,中心点位置会有两个,此时我们需要考虑选择左中点还是右中点,同样分为三种情况:

①:目标元素(不重复)

在这种情况下中点的选择不会影响最终结果,因为目标元素不重复,所以选择左中点或右中点皆可

②:连续序列的左端点

在这种情况下,左右中点的选择会影响判断,看下面这种极端情况:

遍历到区间只剩两个元素时,cur应该更新成left(左中点)还是right(右中点)呢?

假如更新成right,由于cur>=目标值,right会指向cur(还是原位置),如此一来就会进入死循环,cur和right会一直在原地踏步,所以cur需要更新成左中点

③:连续序列的左端点

相反地, 当查询的是连续序列的右端点时,cur需更新成右中点:

在实际中时间复杂度为log(n)的算法并不多见,因为高效率的同时,门槛也越高,我们常了解到的二分算法只能在有序数组中使用,如果数组无序,我们就不能保证目标元素在左或右区间,就不能一次排除一般的元素。

📚循环条件

循环条件是 left<right 还是 left<=right ? 其实就是考虑left、right相遇之后要不要进入循环

①:目标元素(不重复)

mid指针每次更新前都会进行一次判断,如果不是目标元素,则更新继续进入循环;当left、right相遇时,同样需要进行判断,如果还不是目标元素,则没有结果,这个判断和前面的判断一致,不需要特殊处理,所以循环条件是left<=right。

②:目标区间的端点

当left与right相遇后, 如果当前值不为目标值,那么更新left或right指针,会正常退出循环;如果当前值是目标值,那么left与right指针都会停留在当前位置,此时就进入了死循环。为了避免死循环,我们需要将循环条件设成left<right,并且在循环外部额外判断一次。

❓只能是有序数组吗

✅其实并不是,二分算法的巧妙就巧妙在,同样适用于一些无序的场景,后面会碰到具体例题。

📖二、具体运用 

1.⼆分查找

难度等级:⭐⭐⭐

题目链接:704. 二分查找 - 力扣(LeetCode)

题目描述:

给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target  ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1


示例 1:输入:nums = [-1,0,3,5,9,12], target = 9 输出: 4 解释: 9 出现在 nums 中并且下标为 4
算法思路

这种情况就是二分查找的基础玩法,查找一个不重复的目标元素, 从待遍历数组的中心位置开始,判断元素大小,如果>目标值,则更新到左区间中点;<目标值,更新到右区间中点。

算法流程

①:定义left、right、mid指针,mid指向left、right的中点位置(左右皆可)

②:判断mid指向元素

       a.>目标值,right指向mid前一个位置,更新mid

       b.<目标值,left指向mid后一个位置,更新mid

       c.=目标值,返回当前位置

③:执行到此处说明数组不存在目标值,返回空

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

2.查找元素的第⼀个和最后⼀个位置

难度等级:⭐⭐⭐⭐

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

题目描述:

给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。

如果数组中不存在目标值 target,返回 [-1, -1]

你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。



示例 1:输入:nums = [5,7,7,8,8,10], target = 8 输出:[3,4]

示例 2:输入:nums = [5,7,7,8,8,10], target = 6 输出:[-1,-1]

示例 3:输入:nums = [], target = 0 输出:[-1,-1]
算法思路

这道题目就是查找连续区间的端点的情况,查找左右端点需要分别进行。在查找时需要注意细节的处理:左右临界的选择、中点的选择 、循环条件(left<right)

算法流程

找区间左端点:

①:定义left、right、mid,mid指向left、right的左中点

②:判断mid元素

       a.<目标值,right指向mid后一个位置,更新mid

       b.>=目标值,left指向mid,更新mid

③:此时left、right相遇,进行判断,如果=目标值,记录下标;否则返回空

找区间右端点:

①:定义left、right、mid,mid指向left、right的右中点

②:判断mid元素

       a.>目标值,left指向mid前一个位置,更新mid

       b.<=目标值,right指向mid,更新mid

③:此时left、right相遇,进行判断,如果=目标值,记录下标;否则返回空

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

3.x的平⽅根

难度等级:⭐⭐⭐

题目链接:69. x 的平方根 - 力扣(LeetCode)

题目描述:

给你一个非负整数 x ,计算并返回 x 的 算术平方根 。

由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。

注意:
不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5 。



示例 1:输入:x = 4 输出:2

示例 2:输入:x = 8 输出:2 解释:8 的算术平方根是 2.82842..., 由于返回类型是整数,小数部分将被舍去。
算法思路

找一个数的平方根,暴力枚举的思路是:在小于该数的数组中从第一个元素开始,判断当前元素的平方是否为目标元素。

用二分算法进行优化: 在小于该数的数组中,从中间元素开始判断,之后更新成左右区间的中点继续判断。

在这道题中并不需要真的建立一个数组,将left、right、mid就直接是对应的值

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

4.⼭峰数组的峰顶

难度等级:⭐⭐⭐⭐

题目链接:852. 山脉数组的峰顶索引 - 力扣(LeetCode)

题目描述:

给定一个长度为 n 的整数 山脉 数组 arr ,其中的值递增到一个 峰值元素 然后递减。

返回峰值元素的下标。

你必须设计并实现时间复杂度为 O(log(n)) 的解决方案。



示例 1:输入:arr = [0,1,0] 输出:1

示例 2:输入:arr = [0,2,1,0] 输出:1

示例 3:输入:arr = [0,10,5,2] 输出:1
算法思路

这道题的暴力枚举思路很好想,从头遍历数组,与后一个元素进行判断,如果大于后一个元素,说明当前位置就是峰顶。

但是题目要求时间复杂度为O(logn) ,说明指引我们用二分算法思想解决:

但是这道题并不是一个有序数组,我们也不能将其变成有序数组(改变了峰顶的下标),那么还能用二分进行解决吗?

✅当然可以,实际上,二分算法并不局限于有序数组,在无序数组中,只要该数组具有二段性,就依然可以使用二分进行解决:

我们可以把数组看成两个区间,其中6是我们的峰顶,而寻找峰顶的问题就转化成了寻找连续区间a的右端点,如此以来就可以用二分进行解决了。

算法流程

①:定义left、right、mid,由于寻找的是区间右端点,根据极端情况判断,mid应该等于left、right的右中点;

②:判断mid元素

       a.<左元素,说明一定在b区间,则right指向mid前一个位置,更新mid

       b.>右元素,说明在a区间,此时可能是峰顶,left指向mid保存当前位置,更新mid

③:此时left、right相遇处即为峰顶

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

5.点名

 难度等级:⭐⭐⭐

题目链接:LCR 173. 点名 - 力扣(LeetCode)

题目描述:

某班级 n 位同学的学号为 0 ~ n-1。点名结果记录于升序数组 records。假定仅有一位同学缺席,请返回他的学号。



示例 1:输入: records = [0,1,2,3,5] 输出: 4

示例 2:输入: records = [0, 1, 2, 3, 4, 5, 6, 8] 输出: 7
算法思路

这道题目是让我们寻找连续数组中缺失的元素,解决方法其实有很多,可以遍历数组,也可以用哈希表解决,但是这道题最快的方法还是二分查找:

同样可以将数组看成a,b两个区间,而题目最终是让我们寻找区间a的右端点,与上一道题目类似,最终我们需要返回的是 区间a的右端点 的下一个下标。

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

📖三、总结

二分算法是一种经典且高效的查询方法,核心在于通过不断将查找范围缩小为一半,从而大幅降低查找的时间复杂度,从 O(n)优化为 O(log⁡n)。要注意的是,算法在实际应用中有几个关键细节,如左右临界的处理、中点的选择,以及避免死循环的循环条件设计。

我通过多个具体例题,我们可以体会到二分算法的灵活性和强大之处:其不仅适用于有序数组,还可在满足一定性质的无序场景中巧妙运用。


以上就是【优选算法篇·第三章:二分算法】的全部内容,欢迎指正~ 

码文不易,还请多多关注支持,这是我持续创作的最大动力! 

Read more

[DeepSeek] 入门详细指南(上)

[DeepSeek] 入门详细指南(上)

前言 今天的是 zty 写DeepSeek的第1篇文章,这个系列我也不知道能更多久,大约是一周一更吧,然后跟C++的知识详解换着更。 来冲个100赞兄弟们 最近啊,浙江出现了一匹AI界的黑马——DeepSeek。这个名字可能对很多人来说还比较陌生,但它已经在全球范围内引发了巨大的关注,甚至让一些科技巨头感到了压力。简单来说这 DeepSeek足以改变世界格局                                                   先   赞   后   看    养   成   习   惯  众所周知,一篇文章需要一个头图                                                   先   赞   后   看    养   成   习   惯   上面那行字怎么读呢,让大家来跟我一起读一遍吧,先~赞~后~看~养~成~习~惯~ 想要 DeepSeek从入门到精通.pdf 文件的加这个企鹅群:953793685(

By Ne0inhk
DeepFace深度学习库+OpenCV实现——情绪分析器

DeepFace深度学习库+OpenCV实现——情绪分析器

目录 应用场景 实现组件 1. 硬件组件 2. 软件库与依赖 3. 功能模块 代码详解(实现思路) 导入必要的库 打开摄像头并初始化变量 主循环 FPS计算 情绪分析及结果展示 显示FPS和图像 退出条件 编辑 完整代码 效果展示 自然的 开心的 伤心的 恐惧的 惊讶的  效果展示 自然的 开心的 伤心的 恐惧的 惊讶的   应用场景         应用场景比较广泛,尤其是在需要了解和分析人类情感反应的场合。: 1. 心理健康评估:在心理健康领域,可以通过长期监控和分析一个人的情绪变化来辅助医生进行诊断或治疗效果评估。 2. 用户体验研究:在产品设计、广告制作或网站开发过程中,通过观察用户在使用过程中的情绪反应,来优化产品的用户体验。 3. 互动娱乐:在游戏或虚拟现实应用中,根据玩家的情绪状态动态调整游戏难度或故事情节,以增加沉浸感和互动性。

By Ne0inhk
最全java面试题及答案(208道)

最全java面试题及答案(208道)

本文分为十九个模块,分别是:「Java 基础、容器、多线程、反射、对象拷贝、Java Web 、异常、网络、设计模式、Spring/Spring MVC、Spring Boot/Spring Cloud、Hibernate、MyBatis、RabbitMQ、Kafka、Zookeeper、MySQL、Redis、JVM」 ,如下图所示: 共包含 208 道面试题,本文的宗旨是为读者朋友们整理一份详实而又权威的面试清单,下面一起进入主题吧。 Java 基础 1. JDK 和 JRE 有什么区别? * JDK:Java Development Kit 的简称,Java 开发工具包,提供了 Java

By Ne0inhk
10分钟打造专属AI助手!ToDesk云电脑/顺网云/海马云操作DeepSeek哪家强?

10分钟打造专属AI助手!ToDesk云电脑/顺网云/海马云操作DeepSeek哪家强?

文章目录 * 一、引言 * 云计算平台概览 * ToDesk云电脑:随时随地用上高性能电脑 * 二 .云电脑初体验 * DeekSeek介绍 * 版本参数与特点 * 任务类型表现 * 1、ToDesk云电脑 * 2、顺网云电脑 * 3、海马云电脑 * 三、DeekSeek本地化实操和AIGC应用 * 1. ToDesk云电脑 * 2. 海马云电脑 * 3、顺网云电脑 * 四、结语 * 总结:云电脑如何选择? 一、引言 DeepSeek这些大模型让 AI 开发变得越来越有趣,但真要跑起来,可没那么简单! * 本地配置太麻烦:显卡不够、驱动难装、环境冲突,光是折腾这些就让人心态崩了。 * 云端性能参差不齐:选错云电脑,可能卡到爆、加载慢,还容易掉线,搞得效率直线下降。 * 成本难控:有的平台按小时计费,价格一会儿一个样,

By Ne0inhk