下面我总结一个二分查找通用模版,为二分查找神器,使用该模版解决力扣上的二分查找几乎所有的问题。
二分算法执行条件
- 给定的有序数组 ,我们要遍历每个数。
- 判断遍历的每个数是否符合给定的条件。(比如判断遍历的每个数是否等于target)
这时我们就要使用二分算法。他的时间复杂度是O(logN) 要优于O(n)因为O(logN)效率几乎和O(1)相等。具体可以看我的置顶博客,时间复杂度比较图
这是引出二分的原理模版,通过该模版我们可以清楚了解二分是如何执行的。
判定第一个不符合条件的
比如给定数组{1,1,1,1,1,0,0,0,0,0} 快速找到第一个0 我们考虑使用二分
left也就是查找不符合条件的第一个值
//left 和 right 是我们取到到第一个值和最后一个值,左右都是闭的都可以取到的有效值
int left = 1;
int right = n;
while (left <= right) {
int mid = left + ((right - left) >> 1);
//重点:主要是这里进行判断条件的思考
if(当前如果等于1符合条件)) {
//是正确的,则我们向右找。
left = mid + 1;
} else {
//否则向左找
right = mid - 1;
}
}
//重点:mid是符合if条件的最后一个数,left = mid+1,循环完之后返回的是left为 不符合if条件 的第一个index,从这里就衍生出下面的模板了
return left;
典型例题
-
- 第一个错误的版本
-
- 寻找峰值(同278,注意判断条件)
-
- 爱吃香蕉的珂可(同278)
-
- 猜数字大小
-
- x 的平方根
查找目标值及边界
//该模板适用于二分查找:在递增排序的数组中查找目标值target
//1.如果目标值不存在,则返回的left是target应该在的位置
//2.如果目标值存在且有重复if (nums[mid] < target),则返回target第一个位置(左边界)即不符合if条件的第一个值,也就是target = nums[mid]的第一个值
//3.如果目标值存在且有重复if (nums[mid] <= target),则返回大于target第一个位置(右边界)即不符合if条件的第一个值,也就是target > nums[mid]的第一个值
int left = 0;
int right = nums.size() - 1; //left 和 right 是我们取到到第一个值和最后一个值,左右都是闭的都可以取到的有效值
while (left <= right)
{
int mid = left + ((right - left) >> 1); //防止越界,并且比除2提升速度
if (nums[mid] < target) {
//找左边界,则返回target第一个位置(左边界)
// (nums[mid] <= target) //找右边界,求的是大于target的第一个数下标
left = mid + 1; //因为mid的数不是解,所以取mid+1
} else {
//right左移
right = mid - 1;
}
}
//mid是符合if条7件的最后一个数,left = mid+1,
//则left返回的是不符合条件的第一个位置
return left;
典型例题
-
- 搜索插入位置 (模板(nums[mid] < target),元素可重复)
-
- 二分查找 (模板查找固定值,35稍微改动)
-
- 寻找比目标字母大的最小字母(模板(nums[mid] <= target),35稍改)
-
- 在排序数组中查找元素的第一个和最后一个位置 (完美模板训练)
-
- 供暖器
-
- 搜索二维矩阵(两次二分)
旋转数组
根据左右区间递增判断
典型例题
int left = 0;
int right = nums.size()-1;
while(left <= right) {
int mid = left + ((right - left) >> 1);
if(nums[right] < nums[mid]) {
//这时说明目标值在右侧,我们需要右移
left = mid + 1;//右移下标
} else if(nums[right] > nums[mid]) {
right = mid;
} else {
//相等,则说明有重复元素,去重
right--;
}
}
-
- 搜索旋转排序数组
-
- 寻找旋转排序数组中的最小值
-
- 寻找旋转排序数组中的最小值-ii