刷题笔记:二分法

前言

  二分查找算法是一个基本但用处十分广泛的算法,但要写出一个没有bug的二分查找算法也不容易,《编程珠玑》一书中提到仅有百分之十的人可以第一次就写出没有bug的二分查找算法,主要原因在于寻找中间区间时数据有可能溢出,以及区间的选择不正确导致死循环,数组越界等等。
  具体到代码上,我认为需要考虑两个最主要的问题:

  1. 左闭右开,还是左闭右闭(熟练使用一种)
  2. 如果区间只剩下一个数或者两个数,自己的写法是否会陷入死循环

leetcode 69

在这里插入图片描述

class Solution {
public:
    int mySqrt(int x) {
        if(x==0) return 0;
        int l=1,r=x,mid,sqrt;
        while(l<=r){
            mid=l+(r-l)/2;
            sqrt=x/mid;
            if(sqrt==mid) return mid;
            else if(mid>sqrt) r=mid-1;
            else l=mid+1;
        }
        return r;
    }
};

  while 循环的条件中为什么是 <=而不是<,因为采用了左闭右闭的写法。初始状态为[left, right],每一次维护完的状态为[mid+1,right]或者是[left,mid-1]。
考虑循环执行到只剩下一个数时,即是否有必要进行检查left==right的情况。
  while 循环什么时候应该终止?搜索区间为空的时候应该终止,意味着你没得找了,就等于没找到嘛。while(left <= right) 的终止条件是 left == right + 1,写成区间的形式就是   [ r i g h t + 1 , r i g h t ] \ [right + 1, right]  [right+1,right],或者带个具体的数字进去 [3, 2],可见这时候区间为空,因为没有数字既大于等于 3 又小于等于 2 的吧。所以这时候 while 循环终止是正确的,直接返回 -1 即可。
下面是左闭右开的写法,循环结束返回l=r,返回l-1是因为题目要求去除小数部分。

class Solution {
public:
    int mySqrt(int x) {
        if(x<=1) return x;
        int l=1,r=x,mid,sqrt;
        while(l<r){
            mid=l+(r-l)/2;
            sqrt=x/mid;
            if(mid>sqrt) r=mid;
            else l=mid+1;
        }
        return l-1;
    }
};

leetcode 34

在这里插入图片描述

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        if(nums.empty()) return vector<int> {-1,-1};
        int l=low_bound(nums,target);
        int r=high_bound(nums,target)-1;
        if(l==nums.size()||nums[l]!=target){
            return vector<int> {-1,-1};
        }
        return vector<int> {l,r};
    }

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

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

  这道题可以看作是自己实现C++ 里的 l o w e r _ b o u n d lower\_bound lower_bound u p p e r _ b o u n d upper\_bound upper_bound 函数。这里我们尝试使用左闭右开的写法 [left,right)。
在使用左闭右闭时,一般免不了多写一两个+1,-1,return,而且在最后处理返回时left,right只有一个是正确答案,极易出错。
而在左闭右开中,终止后返回left 和right是相同的。

 		if(l==nums.size()||nums[l]!=target){
            return vector<int> {-1,-1};
        }

这里考虑短路机制,需要先判断l的位置,防止其数组越界。

leetcode 81

在这里插入图片描述

class Solution {
public:
    bool search(vector<int>& nums, int target) {
        int l=0,r=nums.size()-1,mid;
        while(l<=r){
            mid=l+(r-l)/2;
            if(nums[mid]==target){
                return true;
            }
            if(nums[l]==nums[mid]){
                ++l;
            }else if(nums[mid]<=nums[r]){
                if(target>nums[mid]&&target<=nums[r]){
                    l=mid+1;
                }else{
                    r=mid-1;
                }
            }else{
                if(target>=nums[l]&&target<nums[mid]){
                    r=mid-1;
                }else{
                    l=mid+1;
                }
            }
        }
        return false;
    }
};

  即使数组被旋转过,我们仍然可以利用这个数组的递增性,使用二分查找。对于当前的中点,如果它指向的值小于等于右端,那么说明右区间是排好序的;反之,那么说明左区间是排好序的。如果目标值位于排好序的区间内,我们可以对这个区间继续二分查找;反之,我们对于另一半区
间继续二分查找。
  注意,因为数组存在重复数字,如果中点和左端的数字相同,我们并不能确定是左区间全部相同,还是右区间完全相同。在这种情况下,我们可以简单地将左端点右移一位,然后继续进行二分查找。

leetcode 154

在这里插入图片描述

class Solution {
public:
    int findMin(vector<int>& nums) {
        int  l=0,r=nums.size()-1;
        int mid;
        while(l<r){
            mid=l+(r-l)/2;
            if(nums[mid]<nums[r]){
                r=mid;
            }else if(nums[mid]>nums[r]){
                l=mid+1;
            }else{
                --r;
            }
        }
        return nums[l];
    }
};

上一题判断的是 l e f t = = m i d left==mid left==mid,所以是右移一位left;
这里判断的是 m i d = = r i g h t mid==right mid==right,所以是左移一位right;

leetcode 540

在这里插入图片描述

class Solution {
public:
    int singleNonDuplicate(vector<int>& nums) {
        if(nums.size()==1) return nums[0];
        int l=0,r=nums.size()-1,mid;
        while(l<=r){
            mid=l+(r-l)/2;
            if(nums[mid] == nums[mid ^ 1]){
                l=mid+1;
            }else{
                r=mid-1;
            }
        }
        return nums[l];
    }
};
nums[mid] == nums[mid ^ 1]

通过和1亦或表示下面的条件
{ X  为偶数  X  xor  1 = X + 1 X  为奇数  X  xor  1 = X − 1 \left\{\begin{array}{c}X \text { 为偶数 } \mathrm{X} \text { xor } 1=\mathrm{X}+1 \\X \text { 为奇数 } \mathrm{X} \text { xor } 1=\mathrm{X}-1\end{array}\right. {X 为偶数 X xor 1=X+1X 为奇数 X xor 1=X1

!(mid%2)&&nums[mid]==nums[mid+1])||((mid%2)&&nums[mid]==nums[mid-1])

leetcode 4

在这里插入图片描述
第一种方法,归并两个数组,返回中位数。
排序算法的时间复杂度为O((m+n)log(m+n))

    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        nums1.insert(nums1.end(),nums2.begin(),nums2.end());
        sort(nums1.begin(),nums1.end());
        int n=nums1.size();
        if(n%2==0){
            return (double)(nums1[n/2]+nums1[n/2-1])*0.5;
        }
        else{
            return (double)nums1[n/2];
        }
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值