二分搜索算法核心-----labuladong笔记
二分搜索算法核心-----labuladong笔记
核心思想:
二分查找场景:寻找一个数、寻找左侧边界、寻找右侧边界。而且,我们就是要深入细节,比如不等号是否应该带等号,mid
是否应该加一等等。分析这些细节的差异以及出现这些差异的原因,保证你能灵活准确地写出正确的二分查找算法。
查找框架:
int binarySearch(vector<int>& nums,int target){
int left=0,right=nums.size()-1;
while(...){
//得到中位数
int mid=left+(right-left)/2;
//与目标数相同
if(nums[mid]==target){
...
}
else if (nums[mid]<target){
left=...
}
else if(nums[mid]>target){
right=...
}
}
}
技巧:不出现else,把所有情况用else if写清楚,可以清楚的展现所有细节。
注意:计算 mid
时需要防止溢出,代码中 left + (right - left) / 2
就和 (left + right) / 2
的结果相同,但是有效防止了 left
和 right
太大,直接相加导致溢出的情况。
leecode案例:
一、寻找一个数
搜索一个数,如果存在,返回索引,不存在则返回-1.
class Solution{
public:
//二分搜索框架
int search(vector<int>& nums,int target){
int left=0;
//[left, right]--->right=mid-1
int right=nums.size()-1;
while(left<=right){
int mid=left+(right - left) / 2;
//与目标数相同
if(nums[mid]==target){
return mid;
}
else if (nums[mid]<target){
left=mid+1;
}
else if(nums[mid]>target){
right=mid-1;
}
}
return -1;
}
};
为什么while循环的条件是<=
不是<
?
答:因为初始化 right
的赋值是 nums.length - 1
,即最后一个元素的索引,而不是 nums.length
。
两端都闭的情况[left, right]
,如果左闭右开[left, right)
,索引大小为 nums.length
是越界的,所以我们把 right
这一边视为开区间。
停止搜索的条件:
if(nums[mid]==target){
return mid;
}
如果没找到,则while循环停止,返回-1.
终止条件: left == right + 1
,写成区间的形式就是 [right + 1, right]
,区间没有任何数字。
缺陷:
有序数组 nums = [1,2,2,2,3]
,target
为 2,此算法返回的索引是 2,没错。但是如果我想得到 target
的左侧边界,即索引 1,或者我想得到 target
的右侧边界,即索引 3,这样的话此算法是无法处理的。
二、寻找左侧边界的二分搜索
int left_bound(vector<int>& nums, int target) {
int left = 0;
// 注意right是不可取的所以判断条件是left < right
//[left, right)--->right=mid
int right = nums.size();
// 注意
while (left < right) {
int mid = left + (right - left) / 2;
//看target在哪个区间
if (nums[mid] == target) {
right = mid;
} else if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
// target在左边则缩小右边界
right = mid;
}
}
return left;
}
-
target
不存在时返回什么?答: 如果
target
不存在,搜索左侧边界的二分搜索返回的索引是大于target
的最小索引。如果想让
target
不存在时返回 -1 其实很简单,在返回的时候额外判断一下nums[left]
是否等于target
就行了,如果不等于,就说明target
不存在。需要注意的是,访问数组索引之前要保证索引不越界: -
为什么是
left = mid + 1
和right = mid
?和之前的算法right = mid-1
不一样?答:这个很好解释,因为我们的「搜索区间」是
[left, right)
左闭右开,所以当nums[mid]
被检测之后,下一步应该去mid
的左侧或者右侧区间搜索,即[left, mid)
或[mid + 1, right)
。 -
为什么该算法能够搜索左侧边界?
可见,找到 target 时不要立即返回,而是缩小「搜索区间」的上界
right
,在区间[left, mid)
中继续搜索,即不断向左收缩,达到锁定左侧边界的目的。 -
为什么返回
left
?都一样,终止条件为
left==right
int left_bound(vector<int>& nums, int target) {
int left = 0;
//[left, right]--->right=mid-1 ; <=
int right = nums.size()-1;
// 注意
while (left <= right) {
int mid = left + (right - left) / 2;
//看target在哪个区间
if (nums[mid] == target) {
//right-1成为区间边界,为了找左边界(单独一个数)
right = mid-1;
} else if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
// target在左边则缩小右边界
right = mid-1;
}
}
// 注意如果target不存在则返回-1
return nums[left] == target ? left : -1;
}
这样就和第一种二分搜索算法统一了,都是两端都闭的「搜索区间」,而且最后返回的也是 left
变量的值。
三、寻找右侧边界的二分查找
int right_bound(vector<int>& nums, int target) {
//[left, right]--->right=mid-1 ; <=
int left = 0, right = nums.size()-1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
// 注意继续成立右区间
left = mid + 1;
} else if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid-1;
}
}
// 注意循环结束为left=right+1
return nums[left - 1] == target ? (left - 1) : -1;
//或者left - 1改成right
return nums[right]==target?right:-1;
}
为什么返回left-1
?
答:首先,while 循环的终止条件是 left = right+1
,所以 left
-1 和 right
是一样的,你非要体现右侧的特点,返回 right
好了。