二分搜索几种写法分析

简介

二分搜索(英语:binary search),也叫折半搜索(英语:half-interval search),是一种在有序数组中查找特定元素的搜索算法。所以是用二分查找的前提是数组必须是有序的;平均时间复杂度为 O ( l o g n ) O(log_n) O(logn).

基本二分搜索

基本的二分查找有两种写法,是根据查找的右边界取值来划分的。其取值不同实现细节略微不同,读者要仔细区分,稍不注意程序就会有bug。

例题

给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的位置。

方法一

当右边界取值为数组长度减一时,写法如下:

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

方法二

当右边界取值为数组长度时,写法如下:

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

总结

方法一与方法二的差异就是while循环中是小于还是小于等于,其实是由右边界right的取值有关的,当right = nums.length -1时指向数组最后一个元素,其搜索区间为[left,right]为闭区间,所以搜索条件为left > right时结束;当right = nums.length时,其搜索区间为[left,right)为左闭右开区间,所以搜索条件为left = right时结束。

查找左侧边界的二分搜索

例题

给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置,不存在则返回-1。

示例1

输入:nums = [5,7,7,8,8,10], target = 8
输出:3

方法一

当右边界取值为数组长度减一时,写法如下:

private int leftLimit(int[] nums, int target){
        int left = 0,right = nums.length - 1;
        while (left <= right){ //注意要点1
            int mid = left + (right - left)/2;
            if(nums[mid] >= target){ //注意要点2
                right = mid - 1;
            }else if(nums[mid] < target) {
                left = mid + 1;
            }
        }
    	//注意要点3
        return left < nums.length && target == nums[left] ? left : -1;
}
注意要点

搜索区间为[left,right]为闭区间,所以当left > right时推出循环。

  • 注意要点1:while条件中left <= right的原因上面已经讲解,这里不再赘述。
  • 注意要点2:当条件nums[mid] == target如何缩小区间,缩小左边界还是右边界?(right = mid - 1)

​ 答:可以从四种情况讨论,缩小左边界有两种情况left = midleft = mid -1,很显然left = mid -1是不对的,因为我们要缩小区间至[left,right]使left > right,而当mid = left且right = left + 1情况会出现问题进入死循环。再讨论left = mid时,很显然此种情况当mid = left且right = left + 1也会进入死循环。所以缩小左边界是不可能的。我们再讨论缩小右边界的两种情况right = midright = mid - 1,很显然right = mid也是不对的,因为当满足条件left = mid = right时同样会死循环。所以只能是right = mid - 1时可满足题意。

  • 注意要点3:要注意left > right是退出循环,所以left可能越界(right = nums.length时),所以最后要进行越界判断。因为leftright都可以越界,我们都需要检验。而上述写法只用left也可实现请读者自行体会。

方法二

当右边界取值为数组长度时,写法如下:

private int leftLimit(int[] nums, int target){
        int left = 0,right = nums.length;
        while (left < right){//注意要点1
            int mid = left + (right - left)/2;
            if(nums[mid] >= target){ //注意要点2
                right = mid ;
            }else if(nums[mid] < target) {
                left = mid + 1;
            }
        }
    	//注意要点3
        return left < nums.length && nums[left] == target ? left : -1;
}
注意要点

搜索区间为[left,right)为左闭右开区间,所以当left == right时推出循环,所以要点2缩小区间只需设置right = mid即可。

总结

方法一与方法二几处不同要点上面我已罗列,请读者自行体会总结。

查找右侧边界的二分搜索

例题

给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的结束位置,不存在则返回-1。

示例1

输入:nums = [5,7,7,8,8,10], target = 8
输出:4

方法一

当右边界取值为数组长度减一时,写法如下:

private int rightLimit(int[] nums, int target){
        int left = 0,right = nums.length - 1;
        while (left <= right){
            int mid = left + (right - left)/2;
            if(nums[mid] <= target){
                left = mid + 1;
            }else if(nums[mid] > target){
                right = mid - 1;
            }
        }
        return right >= 0 && target == nums[right] ? right : -1;
}
注意要点

方法和思想与查找左侧边界的二分搜索的方法一类似,只不过是相反

方法二

当右边界取值为数组长度时,写法如下:

private int rightLimit(int[] nums, int target){
        int left = 0,right = nums.length;
        while (left < right){
            int mid = left + (right - left)/2;
            if(nums[mid] <= target){
                left = mid + 1 ;
            }else if(nums[mid] > target){
                right = mid;
            }
        }
        return left >= 1 && nums[left - 1] == target ? left -1 : -1;
}
注意要点

方法和思想与查找左侧边界的二分搜索的方法二类似,只不过是相反

<think>嗯,用户想了解二分查找中的左开右闭区间的实现和应用场景。我先回忆一下二分查找的基本概念。二分查找通常用于有序数组,通过不断缩小搜索范围来快速定位目标元素。常见的区间写法有左闭右闭[left, right]和左闭右开[left, right),但用户提到的是左开右闭(left, right],这不太常见,需要仔细分析。 首先,左开右闭区间意味着左边界不包含在搜索范围内,而右边界包含。例如,初始区间可能是(0, n],即从索引1到n(假设数组从1开始)。这种情况下,循环条件应该是left < right,因为当left等于right时,区间已经无效。然后在中间值的计算上,可能还是用(left + right) // 2,但需要处理边界调整的问题。比如,当中间元素小于目标值时,新的左边界应该是mid,而不是mid + 1,因为左边界是开的,不包含。反之,如果中间元素大于目标值,右边界调整为mid,因为右闭,包含mid。这可能和常见的写法不同,容易出错,需要仔细验证。 然后,应用场景方面,左开右闭可能在处理某些特定边界问题时更方便,比如数组索引从1开始的情况,或者某些数学问题中自然形成的左开右闭区间。例如,在寻找最后一个满足条件的元素时,这种区间划分可能简化逻辑,减少边界条件的判断。需要举一个具体的例子来说明,比如查找插入位置或者寻找左边界/右边界的情况。 接下来,考虑代码实现。假设数组是升序排列,初始left设为0的左开,即初始区间是(-1, n-1],但数组索引通常从0开始,所以可能需要调整初始值。比如,如果数组索引从0到n-1,左开右闭的初始left可以设为-1,right设为n-1。循环条件left < right,计算mid时用(left + right + 1) // 2,因为当区间长度为偶数时,中间位置需要向上取整,避免死循环。调整边界时,如果arr[mid] < target,left = mid,否则right = mid - 1。这样在左开右闭的区间下,最终left的位置会是目标值的右边界或者插入点。 同时,需要注意这种写法可能在某些情况下更容易处理边界,比如寻找最后一个小于等于目标值的位置。例如,在寻找插入位置时,如果目标值存在多个重复元素,左开右闭区间可能更容易定位到正确的位置。需要测试几种情况,比如目标值在数组开头、中间、结尾,或者不存在的情况,确保代码正确。 最后,对比左闭右闭和左开右闭的异同,指出各自的优缺点。左开右闭可能在处理某些特定问题时更简洁,但需要更谨慎地处理中间值和边界调整,以避免死循环或漏查元素。同时,引用相关的参考资料,比如用户提供的引用[1]和[2],说明二分查找的不同实现方式及其应用场景,增强回答的可信度。</think>### 二分查找的“左开右闭”区间实现及场景分析 #### 1. 左开右闭区间的定义与核心逻辑 左开右闭区间表示为$(left, right]$,即左边界不包含在搜索范围内,右边界包含。例如,若初始区间为$(0, n]$(假设数组索引从$1$开始),则实际搜索范围为$1$到$n$。其核心逻辑如下: - **循环条件**:`while left < right`(当`left == right`时区间为空) - **中间值计算**:`mid = (left + right) // 2` - **边界调整**: - 若`arr[mid] < target`,则新区间为$(mid, right]$(左边界排除`mid`) - 若`arr[mid] >= target`,则新区间为$(left, mid]$(右边界包含`mid`) #### 2. Python代码实现 ```python def binary_search_left_open(arr, target): left, right = -1, len(arr) - 1 # 初始区间为(-1, n-1] while left < right: mid = (left + right + 1) // 2 # 向上取整,避免死循环 if arr[mid] < target: left = mid # 左边界排除mid else: right = mid - 1 # 右边界调整到mid-1 return left + 1 # 最终位置为插入点或目标值的起始位置 ``` #### 3. 应用场景分析 1. **查找最后一个小于等于目标值的位置** 例如,在数组$[1, 2, 3, 3, 4]$中查找最后一个$3$的位置。左开右闭区间可自然定位到索引$3$,无需额外边界判断[^1]。 2. **插入位置的高效确定** 若目标值不在数组中,算法返回其应插入的位置。例如,在$[2, 4, 6]$中查找$5$,返回索引$2$(插入在$4$和$6$之间)。 3. **避免重复元素的冗余遍历** 在含重复元素的有序数组中,左开右闭可减少比较次数,直接逼近目标边界[^2]。 #### 4. 对比其他区间写法 | 区间类型 | 初始边界示例 | 循环条件 | 边界调整特点 | 适用场景 | |---------------|---------------|------------------|------------------------|------------------------| | 左闭右闭$[l, r]$ | `left=0, right=n-1` | `left <= right` | 需要显式排除`mid` | 标准二分查找 | | 左闭右开$[l, r)$ | `left=0, right=n` | `left < right` | 右边界天然不包含 | 查找左边界 | | **左开右闭$(l, r]$** | `left=-1, right=n-1` | `left < right` | 左边界天然不包含 | 查找右边界或插入位置 | #### 5. 注意事项 - **死循环问题**:计算`mid`时需向上取整(`mid = (left + right + 1) // 2`),否则可能导致无限循环。 - **返回值修正**:最终返回`left + 1`,因为`left`是左开区间的最后一个无效位置。 - **数组索引范围**:需根据编程语言或问题需求调整初始值(例如从$0$或$1$开始)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值