写在前面
从零开始的刷题之旅,首先是最基础的数组。
而在数组中,二分查找是一个非常常见的问题
除了我们常见的需要找到某一数之外,还有一些变体比如寻找边界,数组旋转等等。这次就将他们一网打尽。
首先根据一道题来摸清楚二分查找最基础的框架
这样的基础框架是很好写的,只是需要注意细节。
int search(vector<int>& nums,int target){
int left=0;
int right=nums.size()-1;
while(left<=right){
int middle = (left+right)/2;\
if(nums[middle]>target){
right=middle-1;
}
else if(nums[middle]<target){
left=middle+1;
}
else//nums[middle]==target
{
return middle;
}
}
return -1;
}
这就是一个二分查找的基本框架。但是有些细节需要明确。
问题讨论
根据上面的框架,有这么几个问题需要明确:
左闭右开or左闭右闭
可以看到我们的left是0,right是nums.size()-1,也就对应着数组的第一个元素和最后一个元素。
这样的区间就是左闭右闭的,即[left,right]
我们也可以写为左闭右开的,即[left,right),那么right就是nums.size()
这两种写法有什么区别呢?区别在于while里的内容。
如果是左闭右闭,那么while的条件是left<=right,因为right是存在的,所以在两者相等时[left,right]也是有效的。(当然对于这道题来说,由于是升序序列所以不存在相等)
如果是左闭右开,那么while的条件是left<right,当left=right的时候这个区间就没有意义了,所以退出循环。
while中“=”的影响
知道什么时候加等于什么时候不加之后,我们来看循环内的代码。
- middle在left和right的中间,需要注意一般写为left + ((right - left) / 2) 防止溢出。
- 如果middle正好是我们要找的target,那么直接返回即可。
- 如果要找的target在middle的左边,那么我们理所当然要缩小右边界,缩到middle这里
- 如果是左闭右闭,那么我们的缩小区间应该是[left,middle-1],所以right=middle-1
- 如果是左闭右开,那么我们的缩小区间应该是[left,middle),所以right=middle
- 如果要找的target在middle的右边,那么我们理所当然要缩小左边界,缩到middle这里
- 如果是左闭右闭,那么我们的缩小区间应该是[middle+1,right],所以left=middle+1
- 如果是左闭右开,那么我们的缩小区间应该是[middle+1,right),所以left=middle+1
所以到这里我们也应该会写第二种左闭右开的写法了:
int search(vector<int>& nums,int target){
int left=0;
int right=nums.size();
while(left<right){
in