tag数组-刷题预备知识-4.一通百通解决二分查找问题
文章目录
1. 二分法查找法的基本思想
- 二分法其实就是使用了分治法的思想;
- 二分法的基本思想是将n个元素分成大致相等的两部分, 取nums[mid] 与 target 做比较:
- 如果 target = nums[mid], 则找到target, 运行结束, 返回mid;
- 如果 target > nums[mid], 则只需要在数组nums的右半部分继续搜索 target即可;
- 如果
target < nums[mid]
, 则只需要在数组nums的左半部分继续搜索
target即可;
2. 二分查找的时间复杂度
二分查找法的时间复杂度即为 while循环的次数
总共有n个元素, 不断的折半循环下去就是 n, n/2, n/4,… n/2k (这些都是每次需要进行比较操作的元素个数), 其中k是循环的个数;
3. 二分查找的几个模版
一般而言,当一个题目出现以下特征之一时, 我们应该联想到它是否能够使用二分查找解题:
- 待查找的数组有序或局部有序
- 题目要求我们解题的时间复杂度低于 O(n), 或者直接就要求时间复杂度为O(log n)
二分查找有很多变体, 使用时需要注意查找条件, 判断条件和左右边界的更新方式, 三者配合不好就很容易出现死循环或遗漏区间, 这也是因此二分法总被人说"十个二分九个错"的一大原因;
- 下面我们将介绍几种二分查找的模板:
- 标准的二分查找
- 二分查找左边界
- 二分查找右边届
- 二分查找左右边界
- 二分查找极值点
3.1 模板一: 标准的二分查找
标准的二分查找的使用场景(也是算法题目的解题关键字):
数组元素有序且不可重复
我们以经典例题: lt.704-二分查找 为例, 给出这种要求数组元素有序不可重复的二分查找的模板:
//“搜索一个数,如果存在,返回其索引,否则返回 -1
class Solution {
public int binarySearch(int[] nums, int target) {
//定义每次遍历的左右端点
int left = 0;
int right = nums.length - 1;
//开始遍历
while(left <= right){
//定义中值, 它是判断截取数组前半段还是半段的依据
int mid = left + (right - left)/2;
if(nums[mid] == target){
return mid;
}else if(nums[mid] > target){
//截取前半段
right = mid - 1;
}else if(nums[mid] < target){
//截取后半段
left = mid + 1;
}
}
return -1;
}
- 循环条件:
left <= right
- 中间位置计算:
mid = left + ((right - left)) >> 1
(位移>>1 就是 除法运算 /2
, 比除法效率稍高一些) - 左边界更新:
left = mid + 1
- 右边界更新:
right = mid - 1
- 返回值:
mid
或-1
3.2 模板二: 二分查找边界(左边界, 右边界, 或是左右边界)
- 整这种一套一个模板的真的浪费精力, 直接一招鲜吧:
- 对于二分查找类的问题, 我们统一使用:
//左右边界
int left = 0;
int right = nums.length - 1;
//循环条件
while(left <= right){
int mid = left + (right - left) >> 1;
//边界更新方法根据题目要求
...
}
3.2.1 对于二分法查找左边界的模板
public int bsLeft(int[] nums, int target){
int left = 0;
int right = nums.length -1;
while(lef <= right){
int mid = left + ((right - left) >> 1); // >>1 是位运算符, 相当于 /2, 性能比 /2 稍快
if(nums[mid] < target){
left = mid + 1;
}else if (nums[mid] > target){
right = mid - 1;
}else if(nums[mid] == target) {
//由于是查找左边界, 所以查找到于target相等的元素时, 仍需要向左移动
right = mid - 1; // 持续向左 缩短右边界
}
}
//left 越界判断,,根据具体题目判断. (target > nums所有
if(left >= nums.length || nums[left] != target){
//根据具体题目判断.
}
}
3.2.1 对于二分法查找右边界的模板
public int bsLeft(int[] nums, int target){
int left = 0;
int right = nums.length -1;
while(lef <= right){
int mid = left + ((right - left) >> 1); // >>1 是位运算符, 相当于 /2, 性能比 /2 稍快
if(nums[mid] < target){
left = mid + 1;
}else if (nums[mid] > target){
right = mid - 1;
}else if(nums[mid] == target) {
//由于是查找右边界, 所以查找到于target相等的元素时, 仍需要向右
left = mid + 1; // 持续向右 缩短左边界
}
}
// right 越界判断,,根据具体题目判断.
if(left >= nums.length || nums[left] != target){
//根据具体题目判断.
}
}
3.2 模板三: 二分查找极值点
- 二分查找还有一种有趣的变体是二分查找极值点,之前我们使用nums[mid]去比较的时候,常常是和给定的目标值target比,或者和左右边界比较,在二分查找极值点的应用中,我们是和相邻元素去比,以完成某种单调性的检测。关于这一点,我们直接来看一个例子就明白了。
- 这一题的有趣之处在于他要求求一个局部极大值点,并且整个数组不包含重复元素。所以整个数组甚至可以是无序的——你可能很难想象我们可以在一个无序的数组中直接使用二分查找,但是没错!我们确实可以这么干!谁要人家只要一个局部极大值即可呢。
- 题解: lt.162- 寻找峰值
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)