判断一个数是否存在

// program 1.1
int binarySerarch(vector<int>& nums, int target)
{
    int left;
    int mid;
    int right;

    left = 0;
    right = nums.size();                    // 只能访问 [left, right) 区间中的元素
    
    while(left < right)                     // left < right 意味着 [left, right) 仍然是一个有效的区间, 可以继续搜索
    {
        mid = (right - left) / 2 + left;    // 划分出两个区间, 一个点 [left, mid), mid, [mid + 1, right)

        if (target < nums[mid])             // target 位于左区间
        {
            right = mid;                    // 不应当 -1, 网络上一些写法是有问题的
        }
        else if (target > nums[mid])        // target 位于右区间
        {
            left = mid + 1;
        }
        else // if (target == nums[mid])    // target 恰为mid
        {
            return mid;
        }
    }
    
    return -1;
}
// program 1.2
int binarySerarch(vector<int>& nums, int target)
{
    int left;
    int mid;
    int right;

    left = 0;
    right = nums.size() - 1;                // 只能访问 [left, right] 区间中的元素
    
    while(left <= right)                    // left <= right 意味着 [left, right] 仍然是一个有效的区间, 可以继续搜索
    {
        mid = (right - left) / 2 + left;    // 划分出两个区间, 一个点 [left, mid - 1], mid, [mid + 1, right]

        if (target < nums[mid])             // target 位于左区间
        {
            right = mid - 1;
        }
        else if (target > nums[mid])        // target 位于右区间
        {
            left = mid + 1;
        }
        else // if (target == nums[mid])    // target 恰为 mid
        {
            return mid;
        }
    }

    return -1;
}

寻找一个左侧边界

// program 2.1
int lower_bound(vector<int>& nums, int target)
{
    int left;
    int mid;
    int right;

    left = 0;
    right = nums.size();

    while(left < right)
    {
        mid = (right - left) / 2 + left;    // [left, mid), mid, [mid + 1, right)

        if (target < nums[mid])
        {
            right = mid;
        }
        else if (target > nums[mid])
        {
            left = mid + 1;
        }
        else // if (target == nums[mid])
        {
            right = mid;                    // 与 program 1.1 的唯一区别就在这里
        }
    }

    if(left > nums.size() || nums[left] != target) // 对这里的 if 的意义详见后文
    {
        return -1;
    }

    return left;
}

简化后即可得到:

// program 2.2
int lower_bound(vector<int>& nums, int target)
{
    int left;
    int mid;
    int right;

    left = 0;
    right = nums.size();

    while(left < right)
    {
        mid = (right - left) / 2 + left;

        if (target > nums[mid])
        {
            left = mid + 1;
        }
        else
        {
            right = mid;
        }
    }

    if(left > nums.size() || nums[left] != target)    // 可能依然会越界, 后期验证一下
    {
        return -1;
    }

    return left;
}

这里需要注意一下最后的 if 判断, 实际上,无论是 Python 标准库:

def bisect_left(a, x, lo=0, hi=None):
    """Return the index where to insert item x in list a, assuming a is sorted.
    The return value i is such that all e in a[:i] have e < x, and all e in
    a[i:] have e >= x.  So if x already appears in the list, a.insert(x) will
    insert just before the leftmost x already there.
    Optional args lo (default 0) and hi (default len(a)) bound the
    slice of a to be searched.
    """

    if lo < 0:
        raise ValueError('lo must be non-negative')
    if hi is None:
        hi = len(a)
    while lo < hi:
        mid = (lo+hi)//2
        # Use __lt__ to match the logic in list.sort() and in heapq
        if a[mid] < x: lo = mid+1
        else: hi = mid
    return lo

还是 C++ 给出的 possible implementation, 都没有这里的 if 判断:

template<class ForwardIt, class T, class Compare>
ForwardIt lower_bound(ForwardIt first, ForwardIt last, const T& value, Compare comp)
{
    ForwardIt it;
    typename std::iterator_traits<ForwardIt>::difference_type count, step;
    count = std::distance(first, last);
 
    while (count > 0) {
        it = first;
        step = count / 2;
        std::advance(it, step);
        if (comp(*it, value)) {
            first = ++it;
            count -= step + 1;
        }
        else
            count = step;
    }
    return first;
}

这是因为 Python 和 C++ 中的 lower_bound() 函数被设计为: 返回第一个不小于指定值的元素. 因此如果希望严格找到第一个等于指定值的元素,还需要使用 if 进行进一步判断.

寻找一个右侧边界

在 program 2.2的基础上, 很容易得到下面的代码:

// program 3.1
int upper_bound(vector<int>& nums, int target)
{
    int left;
    int mid;
    int right;
    
    left = 0;
    right = nums.size();

    while (left < right)
    {
        mid = left + (right - left) / 2;

        if (target < nums[mid])             // 恰与 program 2.2 相反
        {
            right = mid;
        }
        else
        {
            left = mid + 1;
        }
    }

    if (left >= nums.size() || nums[left - 1] != target)
    {
        return -1;
    }

    return left - 1;                        // 这里与 program 2.2 不同
}