二分查找(红蓝染色法)

前言:本文深入讨论二分查找在闭区间[]的写法,开区间()的写法,半闭半开[)区间的写法,这三种写法的区别。以及查找元素时如何处理条件大于>大于等于>=小于<小于等于<=,这四种情况。
问题:给你一个有序排列的数组nums={4,5,5,7,8,8,8,9,15,20},请你找到第一个>=8的数的位置,如果所有数都<8,返回数组长度。

一、二分查找在闭区间[]的写法

暴力解法用2层for循环去遍历,时间复杂度为O(n^2),且没有用到数组是有序的这个性质,不推荐
推荐:我们可以定义2个指针L和R,即闭区间[L,R],M指向当前正在询问的数,红色染色表示false,本题即<8;蓝色染色表示true,即>=8;白色背景表示不确定。由于是闭区间,所以针对例题数组,L=0,R=n-1=9,m=L+(R-L)/2=0+(9-0)/2=4(向下取整)
循环不变量:L-1始终是红色;R-1始终是蓝色。

    private int lowerBound(int[] nums, int target) {
        int n = nums.length;
        int left = 0;
        int right = n - 1;                          // [left, right]
        while (left <= right) {
            int mid = left + (right - left) / 2;    // 防止【(l + r) / 2】溢出
            if (nums[mid] < target) {
                left = mid + 1;                     // [mid+1, right]
            } else {
                right = mid - 1;                    // [left, mid-1]
            }
        }
        return left;								// left一定是指向
    }

二、二分查找在半闭半开[)区间的写法

    private int lowerBound2(int[] nums, int target) {
        int n = nums.length;
        int left = 0;
        int right = n;                                  // [left, right)
        while (left < right) {                          // ==也行
            int mid = left + (right - left) / 2;
            if (nums[mid] < target) {
                left = mid + 1;                         // [mid+1, right)
            } else {
                right = mid;                            // [left, mid)
            }
        }
        return left;                                    // left 或者 right都行
    }

三、二分查找在开区间()的写法

    private int lowerBound3(int[] nums, int target) {
        int n = nums.length;
        int left = -1;
        int right = n;                                  // (left, right)
        while (left + 1 < right) {                          // ==也行
            int mid = left + (right - left) / 2;
            if (nums[mid] < target) {
                left = mid;                             // (mid, right)
            } else {
                right = mid;                            // (left, mid)
            }
        }
        return right;                                    // left 或者 right都行
    }

以上三种方法萝卜青菜各有所爱。
讨论完>=。那么>,即可转化为>=x+1<(>=x)-1<=(>x)-1也就是(>=x+1)-1
这里举例一题闭区间[],原题目leetcode链接

给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]。
你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。

    public int[] searchRange(int[] nums, int target) {
        int n = nums.length;
        int start = lowerBound(nums, target);
        if (start == n || nums[start] != target)
            return new int[]{-1,-1};
        int end = lowerBound(nums, target + 1) - 1; // 找到start说明数组中一定有target,所以一定存在end
        return new int[]{start, end};
    }

    private int lowerBound(int[] nums, int target) {
        int n = nums.length;
        int left = 0;
        int right = n - 1; // [left, right]
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] < target) {
                left = mid + 1;  // [mid+1, right]
            } else {
                right = mid - 1; // [left, mid-1]
            }
        }
        return left;
    }

“永无止境,诸君共勉”

  • 7
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值