class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        if(nums.size()==0)
            return vector<int> {-1,-1};

        int l,r,mid;
        l=0,r = nums.size()-1;
        while(l<r){
            mid = (l+r)/2;
            if(nums[mid] >= target)
                r=mid;
            else
                l=mid+1;
        }
        if(nums[r]!=target)
            return vector<int> {-1,-1};
        int res = r;

        l=0,r = nums.size()-1;
        while(l<r){
            mid = (l+r+1)/2;
            if(nums[mid] <= target)
                l = mid;
            else
                r = mid-1;
        }
        return vector<int> {res,r};
    }
};

二分模板:

版本1

当我们将区间[l, r]划分成[l, mid]和[mid + 1, r]时,其更新操作是r = mid或者l = mid + 1,计算mid时不需要加1,即mid = (l + r)/2。

C++/java代码模板:


int bsearch_1(int l, int r)
{
while (l < r)
{
int mid = (l + r)/2;
if (check(mid)) r = mid;
else l = mid + 1;
}
return l;
}
版本2

当我们将区间[l, r]划分成[l, mid - 1]和[mid, r]时,其更新操作是r = mid - 1或者l = mid,此时为了防止死循环,计算mid时需要加1,即mid = ( l + r + 1 ) /2。

C++/java 代码模板:


int bsearch_2(int l, int r)
{
while (l < r)
{
int mid = ( l + r + 1 ) /2;
if (check(mid)) l = mid;
else r = mid - 1;
}
return l;
}
为什么两个二分模板的mid取值不同?

对于第二个模板,当我们更新区间时,如果左边界l更新为l = mid,此时mid的取值就应为mid = (l + r + 1)/ 2。因为当右边界r = l + 1时,此时mid = (l + l + 1)/2,相当于下取整,mid为l,左边界再次更新为l = mid = l,相当于没有变化。while循环就会陷入死循环。因此,我们总结出来一个小技巧,当左边界要更新为l = mid时,我们就令 mid =(l + r + 1)/2,相当于上取整,此时就不会因为r取特殊值 r = l + 1而陷入死循环了。

而对于第一个模板,如果左边界l更新为l = mid + 1,是不会出现这样的困扰的。因此,大家可以熟记这两个二分模板,基本上可以解决99%以上的二分问题,再也不会被二分的边界取值所困扰了。

实现细节:

二分查找时,首先要确定我们要查找的边界值,保证每次二分缩小区间时,边界值始终包含在内。
注意看下面的每张图,最后的答案就是红色箭头指出的位置,也是我们二分的边界值。如果不清楚每次二分时,区间是如何更新的,可以画出和下面类似的图,每次更新区间时,要保证边值始终包含在内,这样关于左右边界的更新就会一目了然。
二分的while循环的结束条件是l >= r,所以在循环结束时l有可能会大于r,此时就可能导致越界,基本上二分问题优先取r都不会翻车。