【代码随想录】二分查找算法总结篇

目录

    • 前言
    • 二分查找
      • 例题一
      • 例题二
      • 例题三
      • 例题四


前言

本篇文章记录了代码随想录二分查找算法的总结笔记,下面我们一起来学习吧!!

二分查找

关于二分查找算法,我在之前的这篇博客里面做了非常多的分析,但是后面做题做着发现二分又不会了,还是感觉自己对二分的边界条件不敏感或者说是没完成理解透彻,那么接下来我会通过对例题的逐步分析让大家不再对二分感到困惑!!

在代码随想录中关于二分查找提供了两种写法,这俩种写法其实就是我们解题的关键,下面我们就来逐个分析两种写法的不同与优势!!

第一种写法(左闭右闭):

// 版本一
class Solution {
public:int search(vector<int>& nums, int target) {int left = 0;int right = nums.size() - 1; // 定义target在左闭右闭的区间里,[left, right]while (left <= right) { // 当left==right,区间[left, right]依然有效,所以用 <=int middle = left + ((right - left) / 2);if (nums[middle] > target) {right = middle - 1; } else if (nums[middle] < target) {left = middle + 1; } else { return middle;}}// 未找到目标值return -1;}
};

Q:第一种写法的区间是左闭右闭,所以我们的循环条件为while (left <= right)?

当left == right是有意义的,为什么有意义呢?因为我们的设定的区间范围内的元素都是有可能为目标值的,假设我们要查找的target在最后一个位置,那么left一直向右缩小区间,最终left一定 == right,此时mid == left == right,找到了直接返回mid即可。

第二种写法(左闭右开):

// 版本二
class Solution {
public:int search(vector<int>& nums, int target) {int left = 0;int right = nums.size(); // 定义target在左闭右开的区间里,即:[left, right)while (left < right) { // 因为left == right的时候,在[left, right)是无效的空间,所以使用 <int middle = left + ((right - left) >> 1);if (nums[middle] > target) {right = middle; } else if (nums[middle] < target) {left = middle + 1; } else { return middle;}}// 未找到目标值return -1;}
};

Q:该写法的区间为[left,right)左闭右开,循环条件为while(left < right)?

循环结束条件为left == right,因为此时的left == right是没有意义的,[left, right) == [left, left) == [left, left - 1]显然是没任何意义的。

Q:注意写法二与写法一right的区别,第一种写法为right = mid - 1,第二种写法为right = mid;为何??

其实本质上它们是一样的,因为写法二的right是右开区间它是取不到的,当right == mid,[left, right) == [left, mid) == [left, mid - 1]!!


上述对于俩种写法我们还并不知道它们的优缺点在哪?适用于何种场景?因为上述的二分查找场景是最简单的,下面我们通过例题来进行分析吧。

例题一

搜索插入位置

在这里插入图片描述

给出写法一的代码:

// 左闭右闭
class Solution {
public:int searchInsert(vector<int>& nums, int target) {int l = 0, r = nums.size() - 1;while (l <= r) {int mid = l + (r - l) / 2;if (nums[mid] < target) {l = mid + 1;} else if (nums[mid] > target) {r = mid - 1;} else {return mid;}}return r + 1;  // 返回l也是可行的}
};

相较于之前的普通二分查找这里就只是返回值改变了,之前的场景是找不到就返回-1,而现在是如果找不到还要返回正确的插入位置。那么对于写法一到底该返回left还是right呢?这里为何最终返回的是right + 1?left与right的位置关系如何呢?下面我们来验证一下:

在这里插入图片描述


另外这里也可以将nums[mid] >= target合并为一步,找到第一个大于等于target元素的位置,注意条件一定不能是nums[mid] <= target, 这样找到的位置是最后一个小于等于target元素的位置,也即第一个大于target元素的位置!!

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

为什么这种合并的写法也可以呢?其实合并的写法就包括了直接在数组中匹配到target对应的元素直接返回的情况,分析如下:

在这里插入图片描述

那么返回 l 或者 r + 1 的话都是符合直接匹配到相应的元素直接返回的,另外还包括了不匹配的情况。

写法二的代码(左闭右开):

// 版本一
class Solution {
public:int searchInsert(vector<int>& nums, int target) {int l = 0, r = nums.size();while (l < r) {int mid = l +(r - l) / 2;if (nums[mid] < target) {l = mid + 1;} else if (nums[mid] > target) {r = mid;} else {return mid;}}return l;  // r也可}
};
// 版本二
class Solution {
public:int searchInsert(vector<int>& nums, int target) {int l = 0, r = nums.size();while (l < r) {int mid = l +(r - l) / 2;if (nums[mid] >= target) {r = mid;} else {l = mid + 1;} }return l;  // r也可}
};

注意写法二返回left或right都可以,假设直接匹配到不直接返回的话也就是按照写法二的版本二,因为最终left一定 ==right 才能结束循环,所以返回left和right都可以。

从这里就可以看出写法二的优势:相较于写法一left != right需要考虑返回的位置,而写法二返回left与right都可以!后续当然我个人也比较推荐写法二哈哈,当然了其实理解透彻了这两种写法其实就是看哪种方便用哪种了,没必要去纠结这个问题。

例题二

我们接着来看下一道题:34. 在排序数组中查找元素的第一个和最后一个位置

在这里插入图片描述

思路:要解决这道题,首先我们得找到第一个大于等于该元素的位置,假设该位置的值不等于target那么就没必要找下去了,直接返回{-1,-1},因为第一个位置的元素都不相等那么后面肯定是没有的,并且如果该位置的索引为数组的个数的话(也就是要找的元素大于数组中所有的元素),此时也直接返回{-1, -1};如果该位置的元素等于target的话,那么很简单我们就继续向后查找最后一个相等元素的位置即可。

代码如下:

// 代码一:
class Solution {
public:int lower_bound(vector<int>& nums, int target) {int l = 0, r = nums.size() - 1;while (l <= r) {int mid = l + (r - l) / 2;if (nums[mid] >= target) {r = mid - 1;} else {l = mid + 1;}}return l;  // r + 1}vector<int> searchRange(vector<int>& nums, int target) {if (nums.size() == 0) return {-1, -1};int start = lower_bound(nums, target);if (start == nums.size() || nums[start] != target)  return {-1, -1};int end = lower_bound(nums, target + 1) - 1;  // 找到第一个大于等于target+1的元素, 那么它减-1其实就为最后一个等于target的位置return {start, end};}
};
// 代码二:
class Solution {
public:int lower_bound(vector<int>& nums, int target) {int l = 0, r = nums.size();while (l < r) {int mid = l + (r - l) / 2;if (nums[mid] >= target) {r = mid;} else {l = mid + 1;}}return l;  }vector<int> searchRange(vector<int>& nums, int target) {if (nums.size() == 0) return {-1, -1};int start = lower_bound(nums, target);if (start == nums.size() || nums[start] != target)  return {-1, -1};int end = lower_bound(nums, target + 1) - 1;  // 找到第一个大于等于target+1的元素, 那么它减-1其实就为最后一个等于target的位置return {start, end};}
};
// 代码三:
class Solution {
public:vector<int> searchRange(vector<int>& nums, int target) {if (nums.size() == 0)return {-1, -1};int l = 0, r = nums.size();while (l < r) {int mid = l + (r - l) / 2;if (nums[mid] >= target) {r = mid;} else {l = mid + 1;}}if (r == nums.size() || nums[r] != target)  return {-1, -1};int pos = r + 1;while (pos < nums.size() && nums[pos] == target) {pos++;}return {r, pos - 1};}
};
// 代码四: 
class Solution {
public:vector<int> searchRange(vector<int>& nums, int target) {int l = -1, r = nums.size();while (l + 1 != r) {int mid = l + (r - l) / 2;if (nums[mid] >= target) {r = mid;} else {l = mid;}}if (r == nums.size() || nums[r] != target) return {-1, -1};int pos = r + 1;while (pos < nums.size() && nums[pos] == target) {pos++;}return {r, pos - 1};}
};
// 代码五: STL大法
// lower_bound找到第一个大于等于target的元素, 并返回它的位置
// upper_bound找到第一个大于target的元素, 第一个大于target的位置-1,
// 即为最后一个小于等于target的位置, 因为前面找到了第一个大于等于target的位置, 所以这里一定能找到最后一个等于target的位置
class Solution {
public:vector<int> searchRange(vector<int>& nums, int target) {if (nums.size() == 0)   return {-1, -1};int l = lower_bound(nums.begin(), nums.end(), target) - nums.begin();if (l == nums.size() || nums[l] != target)  return {-1, -1};int r = upper_bound(nums.begin(), nums.end(), target) - nums.begin();return {l, r - 1};}
};

例题三

下面我们来看这道题:69. x 的平方根

在这里插入图片描述

思路:这道题其实可以从搜索插入位置那道题得到很大的启发,结果返回x的平方根是向下取整的。这里同样的有两种情况,第一情况就是mid * mid刚好与x匹配此时直接返回即可,第二种情况就是找不到刚好匹配的就只能取最后一个小于x的那个位置,即第一个大于等于x的位置-1,所以根据上述一系列结论,我们从搜索插入位置那道题得到启发直接就返回right的位置即可(使用左闭右闭方法)!!

// 方法一: 左闭右闭
class Solution {
public:int mySqrt(int x) {// 特判, 防止出现除0错误if (x <= 1) return x;// 这里的右区间还能进行优化, 因为x的平方根它必定是小于等于x/2的。//所以我们可以将右区间缩小至x / 2, 但是x == 2会出现除零错误, 此时我们要向上取整处理一下, 在外面或者在取mid时都可以int l = 0, r = x;   // r = x / 2 + 1也可while (l <= r) {int mid = l + (r - l) / 2;  if (mid > x / mid) {r = mid - 1;} else if (mid < x / mid) {l = mid + 1;} else {return mid;}}return r;  // l - 1都可}
};
// 优化版:
class Solution {
public:int mySqrt(int x) {// 特判, 防止出现除0错误if (x <= 1) return x;int l = 0, r = x / 2; while (l <= r) {int mid = l + (r - l + 1) / 2;if (mid > x / mid) {r = mid - 1;} else if (mid < x / mid) {l = mid + 1;} else {return mid;}}return r;}
};// 第二种写法: 左闭右开
class Solution {
public:int mySqrt(int x) {// 特判, 防止出现除0错误if (x <= 1) return x;int l = 0, r = x + 1;  // 开区间while (l < r) {int mid = l + (r - l) / 2;  if (mid > x / mid) {r = mid;} else if (mid < x / mid) {l = mid + 1;} else {return mid;}}return l - 1;  // 实际上l与r都是第一个大于等于target的元素,  因此最后一个小于x元素的位置就为 l - 1 or r - 1!!}
};

第一种写法较第二种写法的优点:第一种写法的left与right分别代表第一个大于等于target元素的位置、最后一个小于target元素的位置,它能明确代表俩个位置,但缺点就是返回时要考虑清楚返回left还是right;第二种写法的优点就是可以随意返回left与right的位置,但是它们都只能代表第一个大于等于target元素这一个位置,但是我们清楚了它们之间的关系之后,其实第二种写法是更不容易失误的嘿嘿!!

例题四

最后我们来看一道题:367. 有效的完全平方数

这道题乍一看不就是完全平方数嘛,只是返回true还是false的问题,当x的平方根刚好匹配时返回true,不匹配直接就返回false,可以说比上道题简单了不少,也确实是这样,但是我们不能照搬上面的代码:

// 下面这样是错误的
class Solution {
public:bool isPerfectSquare(int num) {int l = 0, r = num / 2;while (l <= r) {int mid = l + (r - l) / 2;if (mid > num / mid) {r = mid - 1;} else if (mid < num / mid) {l = mid + 1;} else {return true;}}return false;}
};

为何上述代码是错的呢?首先我们的思路肯定没问题,那么就一定是代码方面出现了问题,我们注意到在上一道题中我们的mid是跟num/mid进行比较的,这样是为了防止整数溢出,那么对于这道题能这么干吗?假设x == 5,此时mid = 2,mid == num / mid,返回true,但实际上5的平方根不为2啊返回false才对,为什么这里出现了错误?原因是num / mid是向下取整的,所以我们不能这么干,那就只有老老实实的判断mid * mid与num的大小了,并且我们不能用int来保存了,应该用long或者long long来保存才不会使得整数溢出,代码如下:

class Solution {
public:bool isPerfectSquare(int num) {long long l = 1, r = num;while (l <= r) {long long mid = l + (r - l) / 2;if (mid * mid > num) {   // 不能用除法  会向下取整的r = mid - 1;} else if (mid * mid < num) {l = mid + 1;} else {return true;}}return false;}
};
// 左闭右开
class Solution {
public:bool isPerfectSquare(int num) {long long l = 1;long long r = (long long)num + 1;  while (l < r) {long long mid = l + (r - l) / 2;if (mid * mid > num) {   // 不能用除法  会向下取整的r = mid;} else if (mid * mid < num) {l = mid + 1;} else {return true;}}return false;}
};
// 代码三:
class Solution {
public:int mySqrt(int x) {if(x == 1 || x == 0)return x;long l = -1, r = x;while(l + 1 != r){long mid = (l + r) / 2;if(mid * mid <= x)l = mid;elser = mid; }return l;}
};

注意上述代码三是最为巧妙的一种方式,可以解决取整问题以及边界问题,大家还是可以看我之前的这篇博客去了解。

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

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

相关文章

List Control控件绑定变量

创建基于对话框的mfc项目 添加 List Control控件 右击控件&#xff0c;选择“添加变量” 在初始化对话框代码中增加一些代码 BOOL CMFCApplication3Dlg::OnInitDialog() { //...// TODO: 在此添加额外的初始化代码DWORD dwStyle m_programLangList.GetExtendedStyle(); …

初识Spring Boot

初识Spring Boot SpringBoot是建立在Spring框架之上的一个项目,它的目标是简化Spring应用程序的初始搭建以及开发过程。 对比Spring Spring Boot作为Spring框架的一个模块&#xff0c;旨在简化Spring应用程序的初始搭建和开发过程&#xff0c;以下是Spring Boot相对于传统Spri…

AI视频教程下载:用提示工程在GPT商店构建10个GPTs

你将学到什么&#xff1f; 深入了解ChatGPT平台和GPT商店的生态系统。 开发为多样化应用定制GPT模型的专业知识。 掌握高效内容生成的AI自动化技术。 学习高级提示工程以优化ChatGPT输出。 获取构建AI驱动的数字营销和广告解决方案的技能。 了解如何为SEO写作和优化创建专…

Redis篇 redis基本命令和定时器原理

基本命令和定时器原理 一. exists命令二. del命令三. Expire命令四. ttl命令五. redis的过期策略六. 定时器的两种设计方式七. type命令 一. exists命令 用来判断key的值是否存在 返回值是key的个数 这样写的话&#xff0c;有没有什么区别呢&#xff1f; 效率变低&#xff0c;消…

AI办公自动化:用kimi将子文件夹里面的文件批量重命名

工作任务和目标&#xff1a;一个文件夹下有多个子文件夹 子文件夹中有多个srt文件&#xff0c;需要删除文件名中的english和空格 第一步&#xff0c;在kimi中输入如下提示词&#xff1a; 你是一个Python编程高手&#xff0c;一步步的思考&#xff0c;来编写下面任务的Python脚…

概率论统计——大数定律

大数定律 弱大数定律&#xff08;辛钦大数定律&#xff09; 利用切比雪夫不等式&#xff0c;证明弱大数定律 应用 伯努利大数定理&#xff0c;&#xff08;辛钦大数定理的推论&#xff09; 证明伯努利大数定理 注意&#xff1a;这里将二项分布转化成0,1分布来表示&#xff0c;…

按月爬取天气数据可视化展示

从天气网分析,可以查询每个月的天气情况,这里按照url规则,传入年月,获取数据,最后进行可视化展示,最终效果: 下面是获取过程: 第一步: import requestsdef get_weather(month):url = f"https://lishi.tianqi.com/nanning/{month}.html"response = reques…

从0开始学统计-多个婴儿连续夭折是谋杀吗?

1.什么是小概率事件&#xff1f; 小概率事件是指在一次随机试验中发生概率非常低的事件。一般来说&#xff0c;小概率事件的发生概率远低于一定的阈值&#xff0c;通常取0.05或0.01。在统计学中&#xff0c;这些阈值被称为显著性水平&#xff08;significance level&#xff0…

题解:CF1016E Rest In The Shades

题意 平面上有一个点光源 s s s 并以每秒 1 1 1 单位长度的速度从点 ( a , s y ) (a,sy) (a,sy) 移动到点 ( b , s y ) (b,sy) (b,sy)&#xff0c;其中 s y < 0 sy<0 sy<0&#xff1b;在 x x x 轴正方向上有 n n n 不相交、不接触的挡板&#xff0c;第 i i i …

【Spring Boot】响应式编程

响应式编程 1.WebFlux2.比较 MVC 和 WebFlux2.1 工作方式2.2 Spring MVC 与 Spring WebFlux 的区别2.3 使用 WebFlux 的好处 3.Mono 和 Flux3.1 Mono 和 Flux 是什么3.2 Mono 和 Flux 的区别 4.开发 WebFlux 的流程4.1 注解式开发流程4.2 响应式开发流程 5.用注解式开发实现 He…

基于开源ATmega8 无感BLDC程序移植到ATmega328PB

基于开源ATmega8 无感BLDC程序移植到ATmega328PB &#x1f516;基于Atmel Studio 7.0开发环境。&#x1f955;开源原项目资源地址&#xff1a;https://svn.mikrokopter.de/websvn/listing.php?repnameBL-Ctrl&path%2F&&#x1f4cd;原理图和PCB资源 BL-Ctrl v2.0 in E…

Win32 API

个人主页&#xff1a;星纭-CSDN博客 系列文章专栏 : C语言 踏上取经路&#xff0c;比抵达灵山更重要&#xff01;一起努力一起进步&#xff01; 一.Win32 API 1.Win32 API介绍 Windows这个多作业系统除了协调应⽤程序的执⾏、分配内存、管理资源之外&#xff0c;它同时也是…

Redis内存回收-内存淘汰策略

LFU的访问次数之所以叫做逻辑访问次数&#xff0c;是因为并不是每次key被访问都计数&#xff0c;而是通过运算&#xff1a; 生成0~1之间的随机数R计算 (旧次数 * lfu_log_factor 1)&#xff0c;记录为P如果 R < P &#xff0c;则计数器 1&#xff0c;且最大不超过255访问…

9.任务调度

一、开启任务调度器 1.函数 vTaskStartScheduler() 函数 vTaskStartScheduler()用于启动任务调度器&#xff0c;任务调度器启动后&#xff0c;FreeRTOS 便会开始 进行任务调度&#xff0c;除非调用函数 xTaskEndScheduler()停止任务调度器&#xff0c;否则不会再返回。函数 vTa…

Centos修改系統語言

一、使用命令行修系统语言 1、显示系统当前语言环 [rootkvm-suma ~]# localectl System Locale: LANGen_US.utf8 VC Keymap: cn X11 Layout: cn 2、查看系统支持字符集 [rootkvm-suma ~]# locale -a 2、设置系统语言环境 [rootkvm-suma ~]# localectl set-locale LANGz…

【GESP试卷】2024年03月Scratch四级试卷

2024年GESP03月认证Scratch四级试卷 分数&#xff1a;100 题数&#xff1a;27 一、单选题(共15题&#xff0c;每题2分&#xff0c;共30分) 010203040506070809101112131415CDBBACBCDCDADBA 1、小杨的父母最近刚刚给他买了一块华为手表&#xff0c;他说手表上跑的是鸿蒙&…

【雷丰阳-谷粒商城 】【分布式基础篇-全栈开发篇】【00】补充

持续学习&持续更新中… 守破离 【雷丰阳-谷粒商城 】【分布式基础篇-全栈开发篇】【00】补充 插件IDEAVsCode MavenvagrantDocker解决MySQL连接慢问题启动&#xff08;自动&#xff09;Docker注意切换到root用户远程访问MySQL MyBatisPlus代码地址参考 插件 IDEA Mybati…

【数据挖掘】四分位数识别数据中的异常值(附代码)

写在前面&#xff1a; 首先感谢兄弟们的订阅&#xff0c;让我有创作的动力&#xff0c;在创作过程我会尽最大能力&#xff0c;保证作品的质量&#xff0c;如果有问题&#xff0c;可以私信我&#xff0c;让我们携手共进&#xff0c;共创辉煌。 路虽远&#xff0c;行则将至&#…

STM32F1之OV7725摄像头

目录 1. 摄像头简介 2. OV7725 摄像头简介 3. OV7725 引脚 4. OV7725 功能框架图 5. SCCB时序 5.1 SCCB 的起始、停止信号及数据有效性 5.2 SCCB 数据读写过程 1. 摄像头简介 在各类信息中&#xff0c;图像含有最丰富的信息&#xff0c;作为机…

SVM原问题与对偶问题

目的&#xff1a;求出我们的f(X)&#xff0c;它代表着我们X映射到多维的情况&#xff0c;能够帮我们在多维中招到超平面进行分类。 1.优化问题&#xff1a; 1.1推荐好书&#xff1a; 1.2 优化理论中的原问题&#xff1a; 原问题和限制条件如下&#xff1a; 这是一个泛化性…