letcode34在排序数组中查找元素的第一个和最后一个位置

明摆着挺经典的二分题。


用我以前做过的题,能直接想出这样思路:二分找到小于目标的最大值,再找到大于目标的最小值。然后由此得到题目要求。思路很简单,细节是魔鬼。
麻溜的写出了这样的代码

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        int len = nums.size();
        int l=0,  r=len-1;
        while(l<=r){
            int mid = l+(r-l)/2;
            if(nums[mid] >= target)
                r = mid - 1;
            else l = mid + 1;
        }
        int ansmin = r;
        l = 0, r = len-1;
        while(l<=r){
            int mid = l+(r-l)/2;
            if(nums[mid] <= target) l = mid + 1;
            else r = mid - 1;
        }
        int a, b;
        int ansmax = l;
        if(ansmin == -1){
            if(ansmax == len){
                if(len>0) a=0, b=len-1;
                else a=-1, b=-1;
            }
            else {
                if(ansmax == 0) a=-1, b=-1;
                else a=0, b=ansmax-1;
            }
        }
        else{
            if(ansmax == len){
                if(ansmin == len-1) a=-1, b=-1;
                else a=ansmin+1, b=len-1;
            }
            else{
                if(ansmax-ansmin == 1) a=-1, b=-1;
                else a=ansmin+1, b=ansmax-1;
            }
        }
        vector<int> v;
        v.push_back(a), v.push_back(b);
        return v;
    }
};

通过样例发现了写小bug,改完之后一发入魂,很舒服。
但是其实这样在写的时候一点都不舒服。要把每一种情况都列出来,采用三层if的结构才能写清楚。因为找到第一个小于目标的最大值后,还要确保到底存不存在目标值,总之很繁琐,不能算错,但很繁琐。

我写完也感觉很繁琐,于是看了看评论,才发现可以用二分控制while()里面的逻辑表达式,以此来找到目标的左边界和有边界。
以前只会写while(l<=r)这样的形式,并且保证这种情况可以写清楚下面的各种细节,经过不断的归纳总结思考,可以做到。但是却并没有去归纳总结关于while(l<r)这样的形式。本来认为这两种能实现的功能一毛一样,只需处理细节就行,其实并不是。

比如这题,可以直接通过while(l<r)的形式找到符合目标的左边界,而while(l<=r)却并不行,,,,
原因:while(l<=r)这种形式,下面不能出现r=mid,不然就会陷入无限循环的情况,仔细列举几种情况很好想出来。而不能用r=mid就不能把目标值保留下来。也就无法找到符合目标的左边界。

于是有了下面的代码:顺便用了刚学到的一些c++知识,舒服。

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        vector<int> v(2, -1);
        if(nums.empty()) return v;
        int len = nums.size();
        int l = 0, r = len-1;
        while(l < r){
            int mid = (l+r)/2;
            if(nums[mid] >= target) r = mid;//注意这里
            else l = mid+1;//注意这里
        }
        if(nums[l] != target) return v;
        v[0] = l;
        r = len;
        while(l < r){
            int mid = (l+r) / 2;
            if(nums[mid]<=target) l = mid+1;
            else r = mid;
        }
        v[1] = l-1;
        return v;
    }
};

真·短小精悍
总算感觉自己真正完整的会用了二分了。

总结一下关于二分算法
二分主要考察人的,我觉得不是这个算法的思想(这个算法的思想真的挺简单的)而是细节,关于while里面的逻辑运算的细节,关于中间值和目标比较的逻辑运算的细节以及条件成功之后l和r的走势的细节。
弄懂每个细节到底为什么,那二分自此以后再也没有任何问题。
细节是魔鬼。

发布了75 篇原创文章 · 获赞 26 · 访问量 7665

猜你喜欢

转载自blog.csdn.net/qq_40962234/article/details/104582003