当前位置: 首页 > news >正文

我爱学算法之—— 二分查找(上)

了解二分算法

二分查找,想必多多少少有一点了解了,我们了解的二分查找算法:

当一个数组有序的时候,我们可以使用二分算法来查找一个值;

直接比较mid((left + right)/2)和我们要查找的值target;如果nums[mid] > target就在右边查找,否则在左边查找。

但是二分查找真的如此简单吗?在什么时候才能使用二分查找呢?

使用二分查找的条件:数组有序;其实本质是是利用二段性

简单来说,将数组分为;两个区间,一个区间内是满足某个条件;而另一个区间是满足其相反的条件的。

现在我们来通过了解二分查找的算法题,来深入探究二分查找,以及什么时候能够使用二分查找。

一、二分查找

题目解析

在这里插入图片描述

这道题,想必之前已经见到过了;

给定一个数组nums和一个值target,让我们在nums数组中查找target,如果存在就返回下标;否则返回-1

算法思路

对于这道题,我们可以使用暴力查找:让target和数组nums中所有元素一个一个比较;

暴力解法时间复杂度为O(n);从效率上来说也是非常不错的;

但是暴力解法并没有使用到我们数组有序这一个条件;

我们这里想一下,当我们暴力查找到一个位置mid时,区间[0,mid-1]内的值是不是都是小于mid位置的值的;区间[mid+1,n]内的值是不是都是大于mid位置的值的。

简单来说就是:我们任取一个位置,这个位置的值为x;这个位置左边区间的值都是小于x的,右边区间的值都是大于x的。

那我们是不是就可以将区间划分为两部分:

  • 左边区间的值都是小于x
  • 右边区间的值都是大于x

这样我们任取一个位置,如果这个位置的值x大于我们要找的值target,那就去这个位置左边区间再去找target

如果这个位置的值x大于我们要找的值target,那就去这个位置右边区间内再去找target

那我们大致就理解了如何去找target;但是我们可以取二分点也可以取三分点四分点…,那如何去取mid呢?

这里就不叙述这个问题了,我们取二分点的效率是最高的。

二分整体思路:

  • 首先定义leftright分别指向区间的开始位置和结束位置。

  • midmid = (left + right)/2

  • 比较mid位置和要查找的值target

    如果nums[mid] > target,就去左边区间找,right = mid - 1

    如果nums[mid] < target,就去右边区间找,left = mid + 1

    如果nums[mid] == target,找到了我们要查找的值,返回结果。

  • 查找结束还没有返回结果(leftright错过去了,那就表示数组中不存在target)。

在这里插入图片描述

这里需要注意:

**循环结束的条件:**我们leftright指向的位置都是没有查找过的位置,所以当left > right时,循环才能结束。

**取mid:**这里如果数组过大,left + right就可能超出数据范围,我们使用left + (right - left)/2或者left +(right - left + 1)/2来计算;但是有一个问题,对于数组内数据个数是奇数时,这两种计算方式没有什么影响;但如果数组中数据个数是偶数时,第一种left + (right - left)/2求的mid是偏左的,而left + (right - left +1)/2求出的mid是偏右的。

在这道题中我们感受不到这两种求法的差别,在下面题目中我们就能感受到这两种求法的差别了。
在这里插入图片描述

代码实现

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

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

题目解析

在这里插入图片描述

这道题和上面那一道题不同,上一道题目在nums数组中只存在应该target,而这道题目中可能存在多个target

我们需要找到多个target的起始位置和结束位置。

如果数组中不存在target就返回-1,-1

算法思路

首先还是来看暴力解法:从左到右遍历数组,遇到target,就记录target起始位置,然后继续向后遍历直到某个位置的值不等于target

如果数组中不存在target,那暴力解法最坏情况下的时间复杂度为O(n)

那现在我们来想如何使用二分查找来解决这个问题:

这里相信有人和博主一样,先利用二分查找查找到target的某一个位置,然后向左和向右遍历查找target出现的起始位置和结束位置;但是如果数组中的数据都是target,那我们不也是要查找完整个数组,时间复杂度也是O(n)

这里我们就不使用上面二分算法划分区间的方法了,因为我们这里target不一定只出现一次,我们找到target时不能直接返回,因为我们不确定是否还存在其他target

这里我们要查找的是target的起始位置和结束位置,说白了就是左边界和右边界。

二分算法查找左边界:

这里我们查找到target不能直接返回,那就试着将nums[mid] == target划分到左边或者右边的情况;

简单来说就是这里要找的是大于等于t区间的左边界,我们将数组划分成两部分:

  • 左边区间内的值都是小于target的。
  • 右边区间内的值都是大于等于target的。

这样我们在使用二分查找时:

  • 如果nums[mid] < target,那就可以直接舍去[left , mid-1]mid位置的(left = mid + 1);
  • 如果nums[mid] >= target,我们的mid位置的值可能等于target,所以我们只能舍去区间[mid + 1 , right];(right = mid)(这里我们要找的是左边界,如果mid位置的值是等于mid+1位置的值时,我们是可以舍去mid+1位置的

这里我们要求的是大于等于target区间的左边界,所以划分成小于x和大于等于x的两个区间

在这里插入图片描述

这里要注意:

**循环结束条件:**这里我们当left == right时,循环就结束了;所以循环的条件是left < right而不是left <= right

  • 这里left == right时是不需要判断的,因为此时就是最终结果:

    数组中存在大于等于target的区间,也存在小于target的区间,此时leftright相等时指向的就是大于等于target区间左端点的位置

    数组中如果所有数都大于等于target,此时right最终会指向left的位置也就是数组的起始位置,也是大于等于target区间的左端点的位置。

    数组中如果所有数都小于target,此时left最终最指向left的数组的结束位置,也就是right

  • 如果left == right判断了,可能会陷入死循环

    因为这里当nums[mid] >= target时,right = mid;这样如果最后leftright指向的位置是大于等于target的,求出的mid是等于leftright的,那此时就会陷入死循环。

mid的值:

在上面朴素的二分查找算法中,我们利用哪一种求法都可以,但是在这里就不一样了;

如果数据个数是偶数个,利用mid = left + (right - left)/2求出的mid是偏左的;利用mid = left + (right - left + 1)/2求出来的mid是偏右的;

这里我们要找的是区间的左边界,我们要使用mid = left + (right - left)/2来求mid

因为最后如果leftright指向两个相邻的位置(left + 1 = right),利用第一种方法求出来的mid是等于left的;利用第二章方法求出来的mid是等于right的;

如果我们right位置的值的大于等于target的,如果求出的mid是等于right的,此时就会陷入死循环;(因为nums[mid] >= target时,right = mid

在这里插入图片描述

二分算法查找右边界:

和查找左边界类似:

我们要查找的是小于等于target区间的有边界,我们可以根据要查找的位置将数组划分成两部分:

  • 左边区间内的值都是小于等于target
  • 右边区间内的值都是大于target

在二分查找的过程中:

  • 如果nums[mid] <= targetmid位置可能就是最终要查找的结果,所以只能舍去区间[left , mid-1]left = mid);(这里查找的是区间的右边界,所以即使mid-1位置的值等于mid位置的值,也是直接可以舍去的
  • 如果nums[mid] > target,区间[mid , right]内的值都是大于target的,可以舍去区间[mid , right]right = mid - 1)。

这里我们要查找的是小于等于target区间的右边界所以划分为:小于等于target和大于target两区间

在这里插入图片描述

这里也要注意:

循环条件left < right而不是left<=right

mid

在求左边界时使用的是mid = left + (right - left)/2,这样在偶数个数据时求的是偏左位置的;

这里我们要使用mid = left + (right - left + 1)/2,这样当数组在数据个数是偶数个时,求出的mid是偏右的。

因为最后如果leftright指向两个相邻的位置(left + 1 = right),利用第一种方法求出来的mid是等于left的;利用第二章方法求出来的mid是等于right的;

如果我们left位置的值的小于等于target的,如果求出的mid是等于left的,此时就会陷入死循环;(因为nums[mid] <= target时,left = mid

在这里插入图片描述

代码实现

class Solution {
public:vector<int> searchRange(vector<int>& nums, int target) {//数组为空if(nums.empty())    return {-1,-1};int n = nums.size();int begin = 0,end = 0;//求大于等于target区间的左边界int left = 0,right = n-1;while(left < right){int mid = left + (right - left)/2;if(nums[mid] >= target) right = mid;else    left = mid + 1;}//判断是否存在targetif(nums[left] != target)    return {-1,-1};begin = left;//求小于等于target区间的右边界left = 0,right = n-1;while(left < right){int mid = left + (right - left + 1)/2;if(nums[mid] <= target) left = mid;else    right = mid -1;}end = right;return {begin,end};}
};

简单总结

这里两道题,算是最基本的二分算法题,我们一定要理解,理解之后在之后的二分算法题目再深入探究二分算法。

相关文章:

  • 力扣HOT100——102.二叉树层序遍历
  • 解构与重构:“整体部分”视角下的软件开发思维范式
  • File,IO流,字符集
  • 25【干货】在Arcgis中根据字段属性重新排序并自动编号的方法(二)
  • 基于Tcp协议的应用层协议定制
  • Flask + ajax上传文件(三)--图片上传与OCR识别
  • 安服实习面试面经总结(也适合hvv蓝初)
  • 坚果派已适配的鸿蒙版flutter库【持续更新】
  • 什么是Lua模块?你会如何使用NGINX的Lua模块来定制请求处理流程?
  • 从“拼凑”到“构建”:大语言模型系统设计指南!
  • 【开源】基于51单片机的温湿度检测报警系统
  • WPF实现类似Microsoft Visual Studio2022界面效果及动态生成界面技术
  • 矫平机终极指南:特殊材料处理、工艺链协同与全球供应链管理
  • AI日报 - 2025年04月26日
  • 嵌入式学习笔记 - HAL_xxx_MspInit(xxx);函数
  • Prometheus、Zabbix和Nagios针对100个节点的部署设计架构图
  • Python基于Django的全国二手房可视化分析系统【附源码】
  • 2025第十六届蓝桥杯大赛(软件赛)网络安全赛 Writeup
  • 推荐三款GitHub上高星开源的音乐搜索平台
  • proxychains4系统代理for linux(加速国内github下载速度,pip安装)
  • 重新认识中国女性|婚姻,古代传统家庭再生产的根本之道
  • 政治局会议深度|提出“设立新型政策性金融工具”有何深意?
  • 美联合健康集团高管枪杀案嫌疑人对谋杀指控不认罪
  • 因商标近似李小龙形象被裁定无效,真功夫起诉国家知产局,法院判了
  • 上海银行一季度净赚逾62亿增2.3%,不良贷款率与上年末持平
  • 神二十成功对接空间站