二分查找——部分题目汇总——长长文详细分享
文章目录
- 前言
- 二分查找
- 第一题:二分查找
- 解题思路
- GIF
- 代码
- 第二题:搜索插入位置
- 解题思路
- 代码
- 第三题:寻找旋转排序数组中的最小值
- 解题思路
- 代码
- 第四题:寻找峰值
- 解题思路
- 代码
- 第五题:在排序数组中查找数字Ⅰ
- 解题步骤
- 代码
前言
这篇文章我整理了力扣上一些难度偏低和适中的二分查找例题,整理了一些我对二分查找的浅见和方法,题解也仅是通俗讲解,并非代表最好,希望帮助 大家能够更好地理解二分查找。本人代码和见识有限,,有错误欢迎指出!
二分查找
二分查找也叫作折半查找法,每次查找一次便缩小一次范围,关键就在于边界问题和范围的缩小
第一题:二分查找
首先我们举一个比较经典的例子:力扣:二分查找
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
我们先随便举出一种情况:
题目变量:int search(int* nums, int numsSize, int target)
解题思路
我们首先定义两个边界
int left = 0,right = numsSize - 1
,需要注意的是我所定义的right变量是最后一个元素的下标。关于循环部分:
while(left < right)
,因为我的左边界left和右边界right都是可以取到的下标(不会越界访问),当left = right
的时候结束循环,如果按照这种方法写while(left <= right)
的话,部分情况会出现死循环的情况然后再定义
int mid = left + (right - left) / 2;
,除了这个表达式之外,还有一个int mid = (left + right) / 2;
其实这两个表达式的运算结果都是一样的,但是前者能够防止数据溢出的情况如果nums[mid]<target,那就说明mid在target的左边(题意数组是升序的),因为target不可能出现在
[0,mid]
的位置了,所以这时候我们就要调整左边界left的大小,让搜索范围更新成[mid+1,right]
,即left = mid +1;
如果nums[mid]>target,那就说明mid在target的右边,同理,这时候target就不可能出现在mid右边的位置了,所以我们就要调整右边界的大小,减少搜索范围,即
right = mid - 1
。
(为啥是mid-1? 因为nums[mid]已经大于target了,nums[mid]就肯定不等于target,mid就可以从我们的搜索范围中除去)
- 然后重复3,4步骤即可,如果出现了等于的情况,那就不用想了,符合题意,直接返回这个坐标
- 如果二分查找都结束了,也就是left = right 了,我们就要判断nums[left]是否为target,是的话,返回left,否则返回-1;
GIF
(假设target=7,在这个例子中应该返回-1)
代码
int search(int* nums, int numsSize, int target)
{int left=0;int right=numsSize-1;while(left<right){int mid=left+(right-left)/2; //防止溢出if(nums[mid]<target){left=mid+1;}else if(nums[mid]>target){right=mid-1;}else{return mid;}}return nums[left]==target?left:-1;//如果不等于说明不存在,返回-1
}
第二题:搜索插入位置
这个题难度就稍微上升了一丢丢:搜索插入位置
题目:给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
解题思路
所给函数体:int searchInsert(int* nums, int numsSize, int target)
- 首先还是先定义
int left = 0, right = numsSize - 1 ;
和我的循环体模板while(left < right)
- 如果
nums[mid] < target
,那说明插入位置肯定不是mid也不是小于mid的下标,所以我们要把mid以及mid左边的下标排出我们的搜索范围,即left = mid + 1;
- 如果
nums[mid] > target
,那就说明插入位置肯定不在mid右边,但是,可不可以是mid呢?可以看出在下图这种情况的时候,target又刚好不是数组中的元素,此时插入位置恰好是mid,所以mid下标不能移出搜索范围,即更新:right = mid
- 然后按照2和3循环。如果
nums[i] = target
,那就容易了,直接返回 - 但是!以我这种方法,当
nums[] = {1,3,5,6} target = 7
,也就是target比数组中元素都大的时候,二分查找结束的时候,下标left会等于3,但是这时候应该插入的位置应该是4才对,所以在这种情况下,我单独判断二分查找结束时,nums[left]会不会小于target就行啦,如果最后nums[left] < target
,那就返回left+1
代码
int searchInsert(int* nums, int numsSize, int target)
{int right=numsSize-1,left=0;while(left<right){int mid=left+(right-left)/2;if(nums[mid]<target) {left=mid+1;//那么<=mid的地方肯定不是搜素范围}else if(nums[mid]>target){right=mid;//这时候mid有可能是题解,不能-1}else{return mid;}}return nums[left]<target?left+1:left;//判断target会不会比最大的元素还大
}
第三题:寻找旋转排序数组中的最小值
然后是:寻找旋转排序数组中的最小值
已知一个长度为 n 的数组,预先按照升序排列,经由 1 到 n 次 旋转 后,得到输入数组。例如,原数组 nums = [0,1,2,4,5,6,7] 在变化后可能得到:
若旋转 4 次,则可以得到 [4,5,6,7,0,1,2]
若旋转 7 次,则可以得到 [0,1,2,4,5,6,7]
注意,数组 [a[0], a[1], a[2], …, a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], …, a[n-2]] 。
给你一个元素值 互不相同 的数组 nums ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素 。
说直白一点就是,在这种形状中的数组中寻找最小值。
解题思路
其实这个题可以直接一次遍历找出最小值,但是这样就没什么意思了。我分享一下二分法
题目给定函数: int findMin(int* nums, int numsSize)
- 还是一样啊,先定义
int left = 0 ,right =numsSize - 1 ;
还有循环体while(left < right)
,我们依旧是要拿mid跟left和right作对比,根据对比情况得到最小值的位置。 - 如果
nums[mid] > nums[left]
(如下图),这就说明最小值的下标肯定是在mid右边的,不能是mid和mid左边的下标,我们就要更新一下左边界:left = mid + 1
- 如果
nums[mid] < nums[right]
(如下图),我要就要更新一下右边界,但是这里要不要 -1呢,我们分析一下:因为nums[mid] < nums[right]
,然而nums[mid]本身有可能是最小值的,所以mid有可能就是最小值的下标,所以这个地方不能把mid下标移出搜索区域,即right = mid
。 - 说完大于和小于的情况,那会不会出现
nums[mid] == nums[left] 或者 nums[mid]==nums[right]
的情况呢? - ①首先我们先看一下
nums[mid] == nums[right]
:关于表达式int mid = left + (right - left) / 2;
是有向下取整的效果的;(相反int mid = left + (right - left + 1) / 2;
是有向上取整的效果的,不过先不讨论这个),所以不管怎么样,mid不会等于right。 - ②然后是
nums[mid] == nums[left]
,如果出现这种表达式,那么这个二分查找的搜索区间最长就是2(就是只剩两个元素),这样的话就只剩下面两幅图的趋势。如果是左图,那就更新right = mid
,如果是右图,那就是left = mid + 1
。
总结一下:如下
代码
int findMin(int* nums, int numsSize)
{//不断减少查找范围int left=0,right=numsSize-1;while(left<right) {int mid=left+(right-left)/2;if(nums[mid]<nums[right]){right=mid; }else{left=mid+1;}}return nums[left];
}
第四题:寻找峰值
接下来:寻找峰值(学习了力扣大佬的做法)
峰值元素是指其值严格大于左右相邻值的元素。
给你一个整数数组 nums,找到峰值元素并返回其索引。数组可能包含多个峰值,在这种情况下,返回 任何一个峰值 所在位置即可。
你可以假设 nums[-1] = nums[n] = -∞ 。
没想到吧,这种情况下还是可以用二分法。
解题思路
题意所给函数:int findPeakElement(int* nums, int numsSize)
- 还是一样啊,啪的一下把我的模板写出来,很快啊,先定义
int left = 0 ,right =numsSize - 1 ;
还有循环体while(left < right)
- 这次不那么一样。这个题目我们只要找出峰值就行,也就是找出
nums[i-1] < nums[i] < nums[i+1]
。 - 所以我们还是要找出nums[mid],让它和右边一个元素比较即
if(nums[mid] > nums[mid+1])
,如果nums[mid] < nums[mid+1]
,说明还在上升,但是nums[mid]肯定不是峰值,而nums[mid+1]有可能是,所以我们更新左边界left = mid + 1
- 相反,
nums[mid] > nums[mid+1]
,说明nums[mid+1]肯定不是峰值,但是nums[mid]有可能是,既然有可能是,我们就不能把mid移出搜索范围,即更新右边界right = mid
- 然后3,4步骤循环,当left = right的时候二分结束,left即为峰值下标
代码
int findPeakElement(int* nums, int numsSize)
{int left=0,right=numsSize-1;while(left<right){int mid=left+(right-left)/2; //有向下取整的效果if(nums[mid]<nums[mid+1]) //如果是上坡(第七行){ //当二分查找的区间长度时1时,这个过程也就结束了。left=mid+1;}else{right=mid;}}return left;
}
- 补充:上面代码第七行不用判断下标越界的原因是,二分查找的区间长度至少为2,而
int mid = left + (right - left) / 2;
是有向下取整的效果的,所以mid+1一直都在二分查找搜索的范围内
第五题:在排序数组中查找数字Ⅰ
再来一题:在排序数组中查找数字Ⅰ
统计一个数字在排序数组中出现的次数。
当然这个题有别的做法,不过我们还是来尝试一下二分查找法。
解题步骤
所给函数:int search(int* nums, int numsSize, int target)
- 如果这个题用二分查找的方法来写:那么我们只需要找出target出现的第一个下标,再找出target出现的最后一个下标。然后
后者下标 - 前者下标 + 1 = 出现次数
,如图
然后还是一样先定义
int left = 0 ,right =numsSize - 1 ;
还有循环体while(left < right)
所以首先我们就要找出第一个出现target位置的下标:
①如果nums[mid] < target
,那么nums[mid]自然nums[mid] != target
,而mid左边的下标也不可能是target的下标,所以我们更新一下左边界left = mid + 1
②如果
nums[mid] = target
,▲那么nums[mid]有可能是第一次出现target的下标,nums[mid-1]也有可能是第一次出现target的位置,而nums[mid+1]就不可能是第一次出现target的位置了,所以我们就可以更新右边界right = mid
(不能-1,因为▲)③如果
nums[mid] > target
,那么mid和mid右边的下标就不可能是target最后出现的位置,所以:right = mid - 1
//找出第一个出现target的下标
int SearchLeft(int* nums,int numsSize,int target)
{int left=0;int right=numsSize-1;while(left<right) //这个地方需要的是小于号,而不是小于等于号{int mid=left+(right-left)/2;if(nums[mid]<target){left=mid+1;}else if(nums[mid]==target){right=mid;}else{right=mid-1;}}//如果最终还没找到等于target的下标,那就返回0return nums[left]==target? left : 0;
}
然后就要找出最后一次出现target的位置:
①诶那到这里就有点不一样了,我们这时候就要定义int mid = left + (right - left + 1) / 2
这个如上文所说,这个表达式具有向上取整的效果,通俗一点说,就是int mid = left + (right - left) / 2
会尽量靠近left,int mid = left + (right - left + 1) / 2
会尽量靠近right②所以,如果
nums[mid] < target
,那么mid和mid左边的值肯定不会是target处的下标,所以更新左边界left = mid + 1
③如果
nums[mid] = target
,▲mid处有可能是最后一个出现target的,但是mid左边肯定不会是最后一个出现target的,而mid右边也有可能是最后一个出现target的,因此我们缩小左边界left = mid
(不能-1,就是因为▲)④如果
nums[mid] > target
,那么mid和mid右边就不可能是最后一个出现target的,更新右边界right = mid - 1
⑤由于
int mid = left + (right - left + 1) / 2
,所以mid会尽量靠近right,所以我们二分查找结束的时候,要返回nums[right]
,而不是nums[left]
。原因:我们举个例子,数组nums[2] = { 1 , 2 } target = 3
的时候,这时候mid = 1
,而nums[mid] < target
,这时候我们更新left = mid + 1
的时候,left就会越界
int SearchRight(int* nums,int numsSize,int target)
{int left=0;int right=numsSize-1;while(left<right){int mid=left+(right-left+1)/2;if(nums[mid]<target){left=mid+1;}else if(nums[mid]==target){left=mid;}else{right=mid-1;}}//注意这边是nums[right]return nums[right]==target? right : -1 ;
}
代码
int SearchLeft(int* nums,int numsSize,int target)
{int left=0;int right=numsSize-1;while(left<right) {int mid=left+(right-left)/2;if(nums[mid]<target){left=mid+1;}else{right=mid;}}return nums[left]==target? left : 0;
}int SearchRight(int* nums,int numsSize,int target)
{int left=0;int right=numsSize-1;while(left<right){int mid=left+(right-left+1)/2;if(nums[mid]>target){right=mid-1;}else{left=mid;}}return nums[left]==target? left : -1 ;
}int search(int* nums, int numsSize, int target)
{if(numsSize==0){return 0;}//如果找不到第一个出现target的下标,那就肯定找不到最后一个出现target的下标//所以一个返回0,一个返回-1,下面这行代码就会返回0return SearchRight(nums,numsSize,target) - SearchLeft(nums,numsSize,target) + 1;
}
二分查找——部分题目汇总——长长文详细分享相关推荐
- NEFU 大一寒假训练六(二分查找)题目预测
说明 预测原理还是基于这篇文章:NEFU OJ 比赛试题预测 (Python) 为防止格式更正导致文章进入待审核状态,所以今天晚上就不修正格式了 如果有格式问题请自行理解,问题产生主要与Markdow ...
- 【LeetCode 二分查找专项】最长递增子序列(300)(to be polished...)
文章目录 1. 题目 1.1 示例 1.2 说明 1.3 提示 1.4 进阶 2. 解法一(动态规划) 2.1 分析 2.2 解答 2.3 复杂度 3. 解法二(二分查找) 3.1 分析 3.2 解答 ...
- 数据结构与算法——二分查找与二叉查找树汇总整理
目录 预备知识:二分查找基础知识 例1:插入位置(easy) (二分查找) 例2:区间查找(medium)(二分查找) 例3:旋转数组查找(medium)(二分查找) 预备知识:二叉查找(排序)树基础 ...
- 【leetcode】二分查找经典题目
1/x 的平方根 实现 int sqrt(int x) 函数. 计算并返回 x 的平方根,其中 x 是非负整数. 由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去 class Solutio ...
- 程序员面试金典 - 面试题 17.08. 马戏团人塔(最长上升子序 DP/二分查找)
文章目录 1. 题目 2. 解题 2.1 超时解 2.2 二分查找 1. 题目 有个马戏团正在设计叠罗汉的表演节目,一个人要站在另一人的肩膀上.出于实际和美观的考虑,在上面的人要比下面的人矮一点且轻一 ...
- LeetCode4_编写一个函数来查找字符串数组中的最长公共前缀。 如果不存在公共前缀,返回空字符串 ““。(解决方案:横向扫描、 纵向扫描 、分治 二分查找 、秀儿操作之排序比较头尾)
题目 编写一个函数来查找字符串数组中的最长公共前缀. 如果不存在公共前缀,返回空字符串 "". 示例 1: 输入: ["flower","flow&q ...
- 二分查找和二分答案(洛谷)
细节好可怕~ 二分查找算法的细节剖析_JackComeOn的博客-CSDN博客原文:https://www.cnblogs.com/kyoner/p/11080078.html我周围的人几乎都认为二分 ...
- LeetCode 1870. 准时到达的列车最小时速(二分查找)
文章目录 1. 题目 2. 解题 2.1 模拟超时 2.2 二分查找 1. 题目 给你一个浮点数 hour ,表示你到达办公室可用的总通勤时间. 要到达办公室,你必须按给定次序乘坐 n 趟列车. 另给 ...
- 牛客 牛牛的独特子序列(双指针/二分查找)
文章目录 1. 题目 2. 解题 2.1 双指针 2.2 二分查找 1. 题目 链接:https://ac.nowcoder.com/acm/contest/9752/B 来源:牛客网 牛牛现在有一个 ...
最新文章
- mongodb 系列 ~ journal日志畅谈
- 我对自动化测试工程师招聘的建议
- Python爬虫【二】请求库requests
- Ubuntu 14.04使用命令行安装VirtualBox
- java 中映射关系_java – 在Hibernate中映射一对多的关系?
- mysql 变量作用_MySQL变量的用法
- 批量添加PDF帐号目录
- [html] 写一个类似刮刮卡效果的交互,即鼠标划过时显示号码
- ASP.Net中防止页面刷新重复提交的几种方法
- 返回未知项目请重新安装服务器,ppt2003插入图表时显示:“无法找到服务器应用程序,源文件和项目,或返回的未知错误。请重新安装服务程序?(excel显示未知图表)...
- 二进制转十进制python程序_二进制转换(使用Python实现十进制转换器)
- 数据分析之FineReport
- bitcoin全节点搭建
- 思科服务器怎么看型号,通过型号快速识别思科路由器,交换机,服务器等设备
- 攻击JavaWeb应用————2、CS交互安全
- 情侣间为不吵架而“约法三章”,12条可参考理由!
- 中国医药外包市场发展前景预测与竞争态势分析报告2022-2028年版
- chrome浏览器get请求设置header
- mysql中ddl和ddm_分布式数据库DDM Sidecar模式负载均衡
- tl wdr5620虚拟服务器,TP-Link TL-WDR5620无线桥接怎么设置?
热门文章
- 如何使用cmd安装MySQL
- 云原生+大数据 全栈 解决方案!
- yum 报错: If above article doesn‘t help to resolve this issue please use https://bugs.centos.org/.
- 网站url该如何优化更好
- 风车签名管理 for Mac版 - 让签名后的APP可以完全管控和实时监测
- 几种开源SIP协议栈对比
- Docker——docker存储驱动原理
- android wifi调试程序问题
- 什么是SELINUX ?
- uni-app——实现消息提示框