二分查找排序数组中指定数所在区间

本文介绍了解决LeetCode中寻找目标元素起始和结束位置问题的两种方法:使用二分查找法直接搜索和利用自定义的下界查找函数。通过具体的Java代码实现展示了如何高效地解决这一经典算法问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

import java.util.Arrays;

public class Solution {
    //解法1
    public int[] searchRange(int[] nums, int target) {
        if (nums == null || nums.length == 0)
            return new int[]{-1, -1};
        int i = 0;
        int j = nums.length - 1;
        int start = -1, end = -1;
        int mid;
        while (i < j) {
            mid = (i + j) / 2;
            if (target <= nums[mid]) {
                j = mid;
            } else {
                i = mid + 1;
            }
        }
        if (nums[i] == target) {
            start = i;
        } else {
            return new int[]{-1, -1};
        }

        j = nums.length - 1;
        while (i < j) {
            mid = (i + j + 1) / 2;
            if (target >= nums[mid]) {
                i = mid;
            } else {
                j = mid - 1;
            }
        }
        if (nums[i] == target) {
            end = i;
        }

        return new int[]{start, end};
    }

    //解法2
    public int[] searchRange2(int[] nums, int target) {
        if (nums == null || nums.length == 0)
            return new int[]{-1, -1};
        int start = lowerBound(nums, target);
        if (start >= 0 && start < nums.length && nums[start] == target)
            return new int[]{start, lowerBound(nums, target + 1) - 1};
        return new int[]{-1, -1};
    }

    public int lowerBound(int[] nums, int target) {
        int i = 0;
        int j = nums.length;
        int mid;
        while (i < j) {
            mid = (i + j) / 2;
            if (target <= nums[mid]) {
                j = mid;
            } else {
                i = mid + 1;
            }
        }
        return i;
    }

    public static void main(String[] args) {
        int[] nums = {5, 7, 7, 8, 8, 10};
        int target = 8;
        System.out.println(Arrays.toString(new Solution().searchRange(nums, target)));
    }

}

[参考]
https://discuss.leetcode.com/topic/5891/clean-iterative-solution-with-two-binary-searches-with-explanation

<think>根据引用的内容,用户的问题是关于Java实现二分查找,找到有序数组中指定数字的最左边和最右边位置。此外,提供的引用包括二分查找的基本代码、插入排序片段以及Java集合类的描述。但用户的核心需求是实现左右边界的二分查找。我们需要实现两个方法:1.查找左边界:即数组中第一个等于目标的位置。2.查找右边界:即数组中最后一个等于目标的位置。思路:左边界查找:在二分查找中,当中间等于目标时,我们不是立即返回,而是继续在左半部分(即[m+1,u]区间)寻找是否还有更小的等于目标的索引。但实际上,我们可以记录这个位置,然后继续向左查找。更常见的做法是,在找到相等元素时,将上界缩小到m-1(即继续在左半部分查找),最后返回第一个找到的位置。标准方法:查找左边界:初始化:l=0,u=n-1循环条件:l<=u计算中间索引:m=l+(u-l)/2如果a[m]< t,则l=m+1如果a[m]>t,则u=m-1如果a[m]==t,则记录这个位置(可能是左边界),并将u缩小到m-1,以便继续在左半部分查找循环结束后,如果l在数组范围内且a[l]==t,那么l就是左边界,但需要注意的是,循环结束时,l可能大于u,并且可能有多种情况。但更安全的方式是:在循环过程中,我们通过不断缩小右边界来逼近左边界,最后循环结束时的l就是左边界的索引(如果存在),但需要检查l是否越界或者a[l]是否等于t。类似地,查找右边界:在找到相等元素时,我们继续在右半部分(m+1到u)查找,同时记录位置。具体步骤:左边界:intleft=-1;intlow=0;inthigh=arr.length-1;while(low<=high){intmid=low+(high-low)/2;if(arr[mid]< target){low=mid+1;}elseif(arr[mid]>target){high=mid-1;}else{//关键:找到目标后,继续在左半部分找更小的索引(左边界)left=mid;//记录当前找到的位置,然后继续向左找high=mid-1;//调整上界,继续在左半部分查找}}但是这样,当数组中有重复目标时,每次找到目标就记录当前mid,然后向左移动上界。循环结束后,left的就是左边界(如果找到了的话)。如果没有找到,left会保持-1。但是注意:上述代码在找到目标后将high设为mid-1,这样就会向左移动。那么,当没有找到的时候,left就是-1,但还有一种情况,就是数组中根本没有目标,那么整个过程中不会进入else,left就保持-1。但是,上面的写法有一个问题:当mid==0时,high会变成-1,然后退出循环。这样left就保存了0(左边界)。然而,这种方法在查找右边界时也类似,但需要调整。另一种更常用的写法是使用两个独立的函数:查找左边界:使用二分查找,但每次当arr[mid]>=target时,都继续在左半部分查找(但相等时也继续向左),最后low的位置就是左边界(需要检查是否等于target)。实际上,我们可以这样写左边界:intlow=0;inthigh=arr.length;//注意这里使用length,确保左边界可能落在len的位置(如果目标很大)while(low< high){intmid=low+(high-low)/2;if(arr[mid]< target){low=mid+1;}else{high=mid;//这里包括arr[mid]==target时,也向左移,目的是锁定第一个等于的位置}}//此时low就是左边界的索引//检查low是否在数组范围内并且arr[low]==target?但是注意,如果low==arr.length,则越界;如果arr[low]!=target,表示没找到。//实际上,循环结束后,low的范围是[0,arr.length],而且arr[low]可能是第一个大于等于target的位置。所以,如果low在[0,arr.length-1]且arr[low]==target,那么low就是左边界;否则说明不存在。但是用户要求返回具体的左右边界位置,且数组中可能有重复元素。更常用的方法是,分别写两个函数:1.查找第一个大于等于target的元素位置(即左边界)2.查找第一个大于target的元素位置,然后减1就是右边界。具体步骤:左边界:使用二分查找,查找第一个大于等于target的索引。如果该索引的位置等于target,就是左边界;否则不存在。右边界:使用二分查找,查找第一个大于target的索引,然后减去1,就是最后出现的位置。同样要判断这个位置的是否等于target。下面我们用这种方式实现。定义函数:lowerBound:返回第一个大于等于target的索引upperBound:返回第一个大于target的索引那么,左边界就是:如果lowerBound位置的等于target,则返回lowerBound,否则返回-1。右边界就是:如果upperBound-1位置的等于target,则返回upperBound-1,否则返回-1。注意:upperBound返回的是第一个大于target的位置,那么该位置减1就是最后一个等于target的位置。实现lowerBound:intlow=0;inthigh=arr.length;//注意:这里使用开区间[low,high)while(low< high){intmid=low+(high-low)/2;if(arr[mid]< target){low=mid+1;}else{high=mid;}}returnlow;//low的位置是第一个大于等于target的位置实现upperBound:intlow=0;inthigh=arr.length;while(low< high){intmid=low+(high-low)/2;if(arr[mid]<=target){//注意这里,当等于target时,我们也要向右压缩(因为要找大于target的位置)low=mid+1;}else{high=mid;}}returnlow;//第一个大于target的位置然后,使用这两个函数分别获取左右边界。但是,用户要求返回最左和最右的位置,所以我们可以写一个函数返回这两个(或者一个包含两个数组)。注意:检查边界条件:当数组中不存在target时,左右边界都应该返回-1。步骤:left=lowerBound(arr,target);检查left是否在数组范围内并且arr[left]==target,如果是,左边界就是left,否则为-1。right=upperBound(arr,target)-1;检查right是否在数组范围内(right>=0)并且arr[right]==target,如果是,右边界就是right,否则为-1。但是,有可能数组中没有等于target的数,那么left位置的就不是target,这样right位置也不是target。所以我们可以只判断一次:如果左边界是-1,则右边界也返回-1。另外,注意数组为空的情况。我们按照这个思路写代码:这里我们将两个边界写在一个方法中返回。但是注意,我们也可以一次性查找左右边界,不过为了清晰,分别调用两次二分查找也是可以的。由于两次查找的时间复杂度都是O(logn),所以是可以接受的。代码示例:注意:需要处理数组为空的情况。下面我们给出完整的Java代码:</think> ### Java二分查找:有序数组定数的左右边界实现 针对您的需求,我将介绍Java实现二分查找找到有序数组中指定数的左右边界的方法。核心思路是使用两次二分查找:第一次查找左边界(第一个出现的位置),第二次查找右边界(最后一个出现的位置)。 #### 实现代码 ```java public class BinarySearchBoundary { // 查找左边界(第一个等于target的位置) public static int findLeftBound(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 (left < nums.length && nums[left] == target) ? left : -1; } // 查找右边界(最后一个等于target的位置) public static int findRightBound(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 && nums[right] == target) ? right : -1; } // 示例用法 public static void main(String[] args) { int[] nums = {1, 2, 2, 2, 3, 4, 5}; int target = 2; int left = findLeftBound(nums, target); int right = findRightBound(nums, target); System.out.println("左边界: " + left); // 输出1 System.out.println("右边界: " + right); // 输出3 } } ``` #### 算法解析 1. **查找左边界逻辑** - 当 $nums[mid] \geq target$ 时,继续左移右边界 $(right = mid - 1)$ - 终止条件:$left > right$,此时 $left$ 指向第一个等于target的位置 - 时间复杂度:$O(\log n)$ 2. **查找右边界逻辑** - 当 $nums[mid] \leq target$ 时,继续右移左边界 $(left = mid + 1)$ - 终止条件:$left > right$,此时 $right$ 指向最后一个等于target的位置 - 时间复杂度:$O(\log n)$ 3. **边界情况处理** - 未找到元素:返回 -1 - 单元素数组:正常处理 - 全相同元素:返回 [0, n-1] 范围 #### 关键点说明 - **中点计算**:`mid = left + (right - left)/2` 比 `(left+right)/2` 更安全,避免整数溢出 - **重复元素处理**:二分查找左右边界分别处理 - **边界验证**:检查最终索引是否合法且对应匹配 #### 性能特点 - 最优复杂度:$O(\log n)$ - 空间复杂度:$O(1)$ > 该实现遵循标准的二分查找优化原则,确保高效定位边界位置。在Java集合框架中,类似逻辑也用于处理有序集合的边界查找[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值