二分法

目录

    基本的二分法的架构如下代码块所示:

    
      int binarySearch(vector<int>& nums, int target) {
        int left = 0, right = nums.size() - 1;
    
        while(left <= right) {
            int mid = left + (right - left) / 2;//这与(left + right) / 2 的结果相同,只是为了防止太大导致溢出
            if (nums[mid] == target) {
                ...
            } else if (nums[mid] < target) {
                left = mid + 1;//注意,middle已经寻找过了,为此要避开
            } else if (nums[mid] > target) {
                right = mid - 1;//注意,middle已经寻找过了,为此要避开
            }
        }
        return ...;
    }
    


    1.寻找一个数

    Image description

    最直接的解法用find函数,时间复杂度应该是O(N),空间复杂度为O(1)
    
      class Solution {
    public:
        int search(vector<int>& nums, int target) {
            auto result=find(nums.begin(),nums.end(),target);
            if (result<nums.end())
                return result-nums.begin();
            return -1;
        }
    };
    
    二分法:时间复杂度为O(logn),空间复杂度为O(1)
    
      class Solution {
        public:
            int search(vector<int>& nums, int target) {
                //已经排序好的了
                int left=0, right=nums.size()-1;
                while(left<=right)//注意有等号,由于right的初始化,决定了采用的实际上是闭区间
                {
                    int middle=(left+right)/2;
        
                    if(nums[middle]==target)
                        return middle;
                    else if(nums[middle]>target)//证明target在左半区
                        right=middle-1;//注意:由于搜索区间是闭区间,[left, mid-1]
                    else if(nums[middle]<target)
                        left=middle+1;//注意,[mid+1, right] 
                    // mid已经搜索过了,不避开会导致超时~
                }
                return -1;
            }
        };
    
    若上题while(left<=right)改为while(left<right)则应该是:
    
    
    

    while(left <= right) 的终止条件是 left == right + 1(也就是left>right),写成区间的形式就是 [right + 1, right],或者带个具体的数字进去 [3, 2], 可见这时候区间为空,因为没有数字既大于等于 3 又小于等于 2 的吧。所以这时候 while 循环终止是正确的,直接返回 -1 即可。
    while(left < right) 的终止条件是 left == right,写成区间的形式就是 [right, right],或者带个具体的数字进去 [2, 2], 这时候区间非空,还有一个数 2,但此时 while 循环终止了。也就是说区间 [2, 2] 被漏掉了,索引 2 没有被搜索,如果这时候直接返回 -1 就是错误的。必须再次检查最后一个索引!


    2. 左侧边界与右侧边界的二分法查找

    Image description

    要解决这道题,明确以下三种二分法的思路(这个所谓的思路感觉有点问题~按着做容易出错,还是以上题为架构看如何逼近边界较好)
    Image description

    Image description

    Image description

    Click to expand the code,时间复杂度: O(logn),空间复杂度:O(1)
    
      class Solution {
        public:
            int left_bound(vector<int>& nums, int target) 
            {
                int left=0, right=nums.size()-1;
        
                while(left<=right)
                {
                    int mid=(left+right)/2;
                    int value=nums[mid];
                    if(value<target)//target应该在右半区
                        left=mid+1;//中间已经算过了
                    else if(value>target)//应该在左半区
                        right=mid-1;//中间已经算过了
                    else if(value==target)
                        right=mid-1;//一直递推收紧右侧的边界以锁定左侧边界,
                        // 下一个中间值必然是大于或等于,因此可以做到锁定一直更新right
                }
                // 注意要先判断是否有数据越界的情况(由于上面right在找到目标后递推,故此有可能left越界)
                if(left<0  || left>=nums.size())
                    return -1;
                // 判断一下 nums[left] 是不是 target(由于上面right在找到目标后递推,故此有可能left不是目标值但是结束了~)
                return nums[left] == target ? left : -1;
        
            }
            int right_bound(vector<int>& nums, int target) 
            {
                int left=0, right=nums.size()-1;
        
                while(left<=right)
                {
                    int mid=(left+right)/2;
                    int value=nums[mid];
                    if(value<target)//target应该在右半区
                        left=mid+1;//中间已经算过了
                    else if(value>target)//应该在左半区
                        right=mid-1;//中间已经算过了
                    else if(value==target)
                        left=mid+1;//一直地推左侧以锁定右侧的边界
                        // 下一个中间值,必然是小于等于,因此一直推直到锁定右边
                }
                
                // 确保不要越界
                if(right<0  || right>=nums.size())
                    return -1;
        
                // 判断一下 nums[right] 是不是 target
                return nums[right] == target ? right : -1;
        
            }
            vector<int> searchRange(vector<int>& nums, int target) {
        
                // 左侧边界与右侧边界的二分法查找
                int left=left_bound(nums,target);
                if(left==-1)//左边界若不合法,就不用判断右边了~
                    return vector<int>{-1,-1};
                int right=right_bound(nums,target);
        
                return vector<int>{left,right};
            }
        };
    


    求解立方根

    Image description

    Click to expand the code。关键点在于要考虑负号以及小数的情况。且浮点数left<=right会无限运行,需要给个小范围的阈值
    
    
    


    寻找两个正序数组的中位数

    Image description

    Click to expand the code。最直接的做法其实就是合并一个数组,然后排序,但是时间复杂度为:O((M+N)Log(M+N))其实并不满足题目要求(虽然可以通过所有测试样例)
    
    
    
    Click to expand the code,下面的双指针法可以让时间复杂度进一步降低为O(m+n)
    
    
    
    Click to expand the code。而题目要求O(Log(M+N))实际上就是典型的二分法的时间复杂度。
    思路就是在两个有序数组中找到第k 小的元素。其中k就是中位数。(理解得不是很透彻~)
    
    
    


    参考资料