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

目录

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


前言

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

二分查找

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

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

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

// 版本一
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.rhkb.cn/news/330964.html

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

相关文章

【ETAS CP AUTOSAR工具链】ARXML文件详解

本篇文章首先对ARXML这种文件格式做了一个概述&#xff0c;叙述了这种标签语言的基本语法&#xff08;如果您用HTML做过网页&#xff0c;那么这种格式您一定不会陌生&#xff09;&#xff0c;然后对ARXML文件都会包含的一些基本信息做了详细的解读&#xff0c;最后基于使用ISOL…

Java使用apache.poi生成word

加油&#xff0c;打工人&#xff01; 工作需求&#xff0c;将现有的word模板有段落和表格&#xff0c;从数据库中查出数据并填充&#xff0c;word里面也有表格数据&#xff0c;需要将excel表格数据单独处理&#xff0c;然后插入到生成好的word文档中。 下面代码模拟从数据库查出…

力扣刷题---3146. 两个字符串的排列差

题目描述 给你两个字符串 s 和 t&#xff0c;每个字符串中的字符都不重复&#xff0c;且 t 是 s 的一个排列。 排列差 定义为 s 和 t 中每个字符在两个字符串中位置的绝对差值之和。 返回 s 和 t 之间的 排列差 。 示例 1&#xff1a; 输入&#xff1a;s “abc”, t “b…

【安装配置】WSL虚拟机导出、导入镜像(涉及到docker无法在wsl下使用的问题)

背景 WSL&#xff08;Windows Subsystem Linux&#xff09;&#xff0c;是微软提供的在Windows下便携地使用Linux系统的方式&#xff0c;它支持使用虚拟化技术&#xff08;也就是要在bios和控制面板中开启虚拟化支持&#xff09;&#xff0c;完美支持Ubuntu和Windows文件系统之…

泪目!网络连接中断的原因,终于找到了!

朋友们&#xff0c;出大事了&#xff01; 不知道多少朋友玩过 DNF 这个游戏&#xff0c;这个我从小学玩到大学的 “破” 游戏&#xff0c;昨天竟然出手游了&#xff01; 我都忘了自己曾几何时预约过这个手游通知&#xff0c;昨天给我发了条通知信息说游戏已开服。 老玩家直接…

一些常见的程序设计问题

秒杀 redis缓存库存 1.判断库存名额是否充足&#xff0c;2.进行扣减 为了防止超卖&#xff0c;必须保证这两部的原子性 库存扣减后发送mq消息&#xff0c;去异步执行创建订单流程&#xff0c;创建订单失败会造成少卖。可加重试机制&#xff0c;对多次重试依旧失败的&#xff…

基于Netty实现WebSocket服务端

本文基于Netty实现WebSocket服务端&#xff0c;实现和客户端的交互通信&#xff0c;客户端基于JavaScript实现。 在【WebSocket简介-CSDN博客】中&#xff0c;我们知道WebSocket是基于Http协议的升级&#xff0c;而Netty提供了Http和WebSocket Frame的编解码器和Handler&#…

解锁数据关联之道:SQL 表连接详解

文章目录 概述表关系横向连接内连接 inner join左连接 left join右连接 right join全连接 full join交叉连接 cross join 纵向合并UNION ALLUNION 概述 在数据处理、数据分析中常会用到表连接。表连接的作用是将多个表中的数据关联起来&#xff0c;以便在查询过程中获取更全面…

设计模式—23种设计模式重点 表格梳理

设计模式的核心在于提供了相关的问题的解决方案&#xff0c;使得人们可以更加简单方便的复用成功的设计和体系结构。 按照设计模式的目的可以分为三大类。创建型模式与对象的创建有关&#xff1b;结构型模式处理类或对象的组合&#xff1b;行为型模式对类或对象怎样交互和怎样…

正确可用--Notepad++批量转换文件编码为UTF8

参考了:Notepad批量转换文件编码为UTF8_怎么批量把ansi转成utf8-CSDN博客​​​​​​https://blog.csdn.net/wangmy1988/article/details/118698647我参考了它的教程,但是py脚本写的不对. 只能改一个.不能实现批量更改. 他的操作步骤没问题,就是把脚本代码换成我这个. #-*-…

1、pikachu靶场之xss钓鱼复现

一、复现过程 1、payload <script src"http://127.0.0.1/pkxss/xfish/fish.php"></script> 将这段代码插入到含有储存xss的网页上&#xff0c;如下留言板 2、此时恶意代码已经存入数据库&#xff0c;并存在网页中&#xff0c;当另一个用户打开这个网页…

uniapp+canvas实现逐字手写效果

在移动端使用 UniApp 进行逐字手写的功能。用户可以在一个 inputCanvas 上书写单个字&#xff0c;然后在特定时间后将这个字添加到 outputCanvas 上&#xff0c;形成一个逐字的手写效果。用户还可以保存整幅图像或者撤销上一个添加的字。 初始化 Canvas&#xff1a; 使用 uni.c…

使用FFmpeg推流实现在B站24小时点歌直播

使用FFmpeg推流实现在B站24小时点歌直播 本文首发于个人博客 安装FFmpeg centos7 https://www.myfreax.com/how-to-install-ffmpeg-on-centos-7/ https://linuxize.com/post/how-to-install-ffmpeg-on-centos-7/ 使用FFmpeg在B站直播 https://zhuanlan.zhihu.com/p/2395…

超级初始网络

目录 一、网络发展史 1、独立模式 2、局域网 LAN&#xff08;Local Area Network&#xff09; 3、广域网 WAN (Wide Area Network) 二、网络通信基础 1、IP地址&#xff1a;用于定位主机的网络地址 2、端口号&#xff1a;用于定位主机中的进程 3、网络协议 4、五元组 …

基于卷积神经网络的交通标志识别(pytorch,opencv,yolov5)

文章目录 数据集介绍&#xff1a;resnet18模型代码加载数据集&#xff08;Dataset与Dataloader&#xff09;模型训练训练准确率及损失函数&#xff1a;resnet18交通标志分类源码yolov5检测与识别&#xff08;交通标志&#xff09; 本文共包含两部分&#xff0c; 第一部分是用re…

LeetCode 279 —— 完全平方数

阅读目录 1. 题目2. 解题思路3. 代码实现 1. 题目 2. 解题思路 此图利用动态规划进行求解&#xff0c;首先&#xff0c;我们求出小于 n n n 的所有完全平方数&#xff0c;存放在数组 squareNums 中。 定义 dp[n] 为和为 n n n 的完全平方数的最小数量&#xff0c;那么有状态…

mysql中text,longtext,mediumtext区别

文章目录 一.概览二、字节限制不同三、I/O 不同四、行迁移不同 一.概览 在 MySQL 中&#xff0c;text、mediumtext 和 longtext 都是用来存储大量文本数据的数据类型。 TEXT&#xff1a;TEXT 数据类型可以用来存储最大长度为 65,535(2^16-1)个字符的文本数据。如果存储的数据…

【服务器】使用mobaXterm远程连接服务器

目录 1、安装mobaXterm2、使用mobaXterm3、程序后台保持运行状态 1、安装mobaXterm 下载地址&#xff1a;https://mobaxterm.mobatek.net/download.html 下载免费版 分为蓝色便携版&#xff08;下载后可直接使用&#xff09;和绿色安装版&#xff08;需要正常安装后使用&…

【老王最佳实践-6】Spring 如何给静态变量注入值

有些时候&#xff0c;我们可能需要给静态变量注入 spring bean&#xff0c;尝试过使用 Autowired 给静态变量做注入的同学应该都能发现注入是失败的。 Autowired 给静态变量注入bean 失败的原因 spring 底层已经限制了&#xff0c;不能给静态属性注入值&#xff1a; 如果我…

Go语言(Golang)的开发框架

在Go语言&#xff08;Golang&#xff09;的开发中&#xff0c;有多种开发框架可供选择&#xff0c;它们各自具有不同的特点和优势。以下是一些流行的Go语言开发框架&#xff0c;选择Go语言的开发框架时&#xff0c;需要考虑项目需求、团队熟悉度、社区支持、框架性能和可维护性…