真正搞懂二分法

关于二分法,大致思路虽然不难。但下列问题一直没有真正搞懂,做题也经常是通过不断试错解出来的
① while(left < right) OR while(left <= right) ??
② left = mid + 1 OR left = mid ??
③ right = mid - 1 OR right = mid ??
④ return mid OR return left OR return right ??

框架

int binarySearch(int[] nums, int target) {
    int left = 0, right = ...;

    while(...) {
        //int mid = (right + left) / 2; //left+right可能会造成溢出
        int mid = left + (right - left) >> 1;//避免上述情况
        //讨论三种情况
        if (nums[mid] == target) {
            ...
        } else if (nums[mid] < target) {
            left = ...
        } else if (nums[mid] > target) {
            right = ...
        }
    }
    return ...;
}

查找某数

int binarySearch(int[] nums, int target) {
    int left = 0;
    int right = nums.length - 1; 
    //查找范围[left,right],存在左右边界重合的情况,left=right的情况不能落下
    while(left <= right) {
         int mid = left + (right - left) >> 1;
        //找到直接返回
        if(nums[mid] == target)
            return mid;    
        else if (nums[mid] < target)
        	//由于查找范围是左右闭区间,因此改变区间时right,left均加1
            left = mid + 1; 
        else if (nums[mid] > target)
            right = mid - 1; 
        }
    return -1;
}

局限性:当存在数组 nums = [0,2,2,2,3],target = 2,此算法返回索引 2,没错。但是如果想得到 target 的左侧边界索引 1,或者右侧边界索引 3,算法是无法处理的。虽然可以找到一个 target,然后向左或向右线性搜索,但是不好,因为这样就浪费掉了二分查找O(log(n))的时间复杂度。

改进:有多个相同值时寻找左侧边界

int left_bound(int[] nums, int target) {
    if (nums.length == 0) return -1;
    int left = 0;
    int right = nums.length;
    //查找范围[left,right),不存在左右边界重合的情况,left和right无法重合
    while (left < right) { 
         int mid = left + (right - left) >> 1;
        if (nums[mid] == target) {
        //找到符合条件的mid后,由于要找左侧边界,因此将范围缩小至左侧继续搜索
        //[left,right)被分为[left,mid)和[mid+1,right),由于是左闭右开区间,因此若让right = mid - 1,则mid - 1 会被遗漏
            right = mid;
        } else if (nums[mid] < target) {
            left = mid + 1;
        } else if (nums[mid] > target) {
            right = mid;
        }
    }
    return left;
    //不会返回-1,返回值属于闭区间[0,nums.length]
}

存在找不到的情况,可在末尾添加

return nums[left] == target ? left : -1;

改进:有多个相同值时寻找右侧边界

int left_bound(int[] nums, int target) {
    if (nums.length == 0) return -1;
    int left = 0;
    int right = nums.length;
    //查找范围[left,right),不存在左右边界重合的情况,left和right无法重合
    while (left < right) { 
         int mid = left + (right - left) >> 1;
        if (nums[mid] == target) {
        //找到符合条件的mid后,由于要找右侧边界,因此将范围缩小至右侧继续搜索
        //[left,right)被分为[left,mid+1)和[mid+1,right),由于是左闭右开区间,因此若让left = mid,则mid + 1 会被遗漏
            left = mid + 1;
        } else if (nums[mid] < target) {
            left = mid + 1;
        } else if (nums[mid] > target) {
            right = mid;
        }
    }
    return left - 1;      
    //跳出while循环后left = right 因此等价于right - 1
    //不会返回-1,返回值属于闭区间[0,nums.length]
    //由于执行过left = mid + 1 因此最终nums[left]一定不等于target了
}

存在找不到的情况,可在末尾添加

return nums[left-1] == target ? (left-1) : -1;

找左侧

public int findleft(int[] nums,int target)
    {
        if(nums.length == 0)
        return -1;
        int l = 0,r = nums.length - 1;
        while(l < r)
        {
            int mid = l + (r - l)/2;
            if(nums[mid] >= target)
            {
                r = mid;
            }
            else
            l = mid + 1;
        }
        return nums[r] == target?r:-1;
    }

找右侧

public int findright(int[] nums,int target)
    {
        if(nums.length == 0)
        return -1;
        int l = 0,r = nums.length - 1;
        while(l < r)
        {
        //重点 防止死循环
            int mid = l + (r - l + 1)/2;
            if(nums[mid] <= target)
            {
                l = mid;
            }
            else
            r = mid - 1 ;
        }
        return nums[r]==target?r:-1;
    }
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值