【Leetcode】34. 在排序数组中查找元素的第一个和最后一个位置 Medium

 

 

本题并不困难, 但是引发了我对二分查找算法的一些思考。

二分查找算法的实现思想有两种:

思路 1:在循环体中查找元素   

循环内部有三个分支:

    int search(vector<int>& nums, int target) {
        int left = 0, 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 right = mid - 1;
        }
        return -1;
    }

思路 2:在循环体中排除目标元素一定不存在的区间

循环内部有两个分支:

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

排除法这里有很多需要思考, 比如循环终止条件、mid需不需要向上取整。 总结一下编码要点编码要点
循环终止条件写成:while (left < right) ,表示退出循环的时候只剩下一个元素;
在循环体内考虑如何缩减待搜索区间,也可以认为是在待搜索区间里排除一定不存在目标元素的区间;
根据中间数被分到左边和右边区间,来调整取中间数的行为;
如何缩小待搜索区间,一个有效的办法是:从 nums[mid] 满足什么条件的时候一定不是目标元素去考虑,进而考虑 mid 的左边元素和右边元素哪一边可能存在目标元素。一个结论是:当看到 left = mid 的时候,取中间数需要上取整,这一点是为了避免死循环;
退出循环的时候,根据题意看是否需要单独判断最后剩下的那个数是不是目标元素。
边界设置的两种写法:

right = mid 和 left = mid + 1 和 int mid = left + (right - left) / 2; 一定是配对出现的;
right = mid - 1 和 left = mid 和 int mid = left + (right - left + 1) / 2; 一定是配对出现的。
这一点不需要记忆,只要一直思考目标元素可能存在的区间就可以了。因为下一轮搜索的区间是 [left, mid] 所以这个时候设置 right = mid 。当前这条性质不满足的时候,既然整个区间是 [left, right] 区间里,第一种情况所在区间是 [left, mid] ,那么另外一种情况对应的区间是 [mid + 1, right] ,两个区间合起来就是整个区间 [left, right] 。同理,去理解 right = mid - 1 和 left = mid 这两个边界设置。

 

回到原题, 需要使用排除法找出target值的上下边界

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

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

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

 

posted @ 2022-03-08 22:41  鱼儿冒个泡  阅读(21)  评论(0编辑  收藏  举报