对于二分查找来说,最重要一点是要弄清楚如何查找。
所谓如何查找首先要弄清查找的边界,对于给定的的一个范围是[闭区间]还是[左闭右开),这其中牵扯到每一次更新位置的时候mid的定位。
每次拿着中间位置的那个元素和目标元素去进行比较,其实换个角度想也就是将目标所在的位置通过改变left与right来一点点定位出来,这也是贯穿整个二分查找元素位置、左边界、右边界的核心思想。每次和中间位置那个元素比较完之后,除非是相等,否则就将中间的那个元素剔除(因为已经比较过了),向左或者向右来把这个目标所在的范围一点点逼近。
除此之外为了防止int类型溢出,若直接mid=left+right,可能溢出。所以可以mid=left+(right-left)/2
先说闭区间的找法,如果两边都是闭区间,那么右边的边界每次都在有效范围内。这样每次mid都是有效范围内的一员,思路是如果目标在mid位置的左边,那么说明right大了,那么就将right移动到mid-1的位置,从而确定出目标在[left,mid-1]的区间内;如果目标在mid位置的右边,那么说明left小了,那么就将left移动到mid+1的位置上去。
对于查找边界:
查找边界与定位元素最大的不同在于找到了目标元素所在位置以后的做法。所谓两种方法,闭区间查找与左闭右开查找只是形式上的不同罢了,但核心思想相同。当找到了与目标元素一致的元素位置后,因为我们要找的是边界,所以继续逼近。
对于查找左边界来说,如果找到了,就说明还可以再往左边探探,那么这时需要将right放置到刚刚找到的这个位置的左侧;对于查找右边界来说,如果找到了,说明还可以再往右探探,那么就将left放在刚刚找到的这个位置的右边。
最终边界的确定是当left==right,左右重合,说明这就是要寻找的边界。
至于采用闭区间还是左闭右开,只是一个有效范围的问题。若采取闭区间,那么右边的界限始终是有效的,当比较完mid位置时,若向左,那么就right=mid-1,若向右,那么就left=mid+1,严格将mid剔除。
若采用的是左闭右开,那么对于right来说永远取不到,是一个溢出的位置,那么如果向左,right=mid;如果向右,那么left=mid+1(因为left一直是有效范围内)。在最后左右归一的时候,如果取得是右边界,因为右边界永远是溢出的那一位,最后需要left-1,这才是实际上的右边界。
最后可以检查一下代码的越界情况。
35. 搜索插入位置
int searchInsert(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 left;
}
int mySqrt(int x){
long n = x - 1; //元素最右
long left = 0;
long right = n;
if (x == 1) {
return 1;
}
if (x == 0) {
return 0;
}
while (left <= right) {
long mid = left + (right-left) / 2;
if (mid*mid < x) {
left = mid + 1;
} else if (mid * mid > x) {
right = mid - 1;
} else {
return mid;
}
}
return (left - 1 + right)/2;
}
bool isPerfectSquare(int num){
if (num == 1) return true;
long left = 1;
long right = num;
while (left <= right) {
long mid = left + (right - left) / 2;
if (mid * mid > num) {
right = mid - 1;
} else if (mid * mid < num) {
left = mid + 1;
} else {
return true;
}
}
return false;
}
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
if(nums.empty()) return {-1,-1};
int length = nums.size() - 1;
int left = 0, right = length; //二分范围
/*先定左边界*/
while (left <= right) {
int mid = left + (right - left) /2;
if (nums[mid] > target) {
right = mid-1;
} else if (nums[mid] < target) {
left = mid+1;
} else if (nums[mid] == target) {
right = mid-1;
}
}
/*判断下是否有这个元素*/
if (left>length || nums[left]!=target ) {
return {-1,-1};
}
int Left = left;
left = 0;
right = length;
/*确定右边界*/
while (left <= right) {
int mid = left + (right-left)/2;
if (nums[mid] > target) {
right = mid-1;
} else if (nums[mid] < target) {
left = mid +1;
} else if (nums[mid] == target) {
left = mid+1;
}
}
if (right<0 || nums[right] != target) {
return {-1,-1};
}
return {Left,right};
}
};