循环有序数组中查找某个数

本文介绍了一种在循环有序数组中查找特定数值的高效算法,并通过二分查找实现了旋转数组的最小值定位。针对循环有序数组的特点,文章提出了一种策略,即通过判断一半是否有序来缩小搜索范围,从而避免了复杂性较高的循环有序数组的直接处理。此外,还介绍了如何将求最小数的问题转化为寻找旋转数组的转折点。

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

循环有序数组中查找某个数

利用二分查找,  一个循环有序数组从中间切分,一定分成一个有序数组和一个循环有序数组,由于循环有序数组不容易判断,我们只需判断哪一边是有序数组以及target是不是在有序数组这一边。

# 循环有序数组查找某个数
def find(ls, target):
    if not ls or not target:
        return -1
    low = 0
    high = len(ls) - 1
    # 一个循环有序数组从中间切分,一定分成一个有序数组和一个循环有序数组,
    # 由于循环有序数组不容易判断,我们只需判断哪一边是有序数组以及target是不是在有序数组这一边
    while low <= high:
        mid = (low+high) // 2
        if ls[mid] == target:
            return mid
        # 若左半部分为有序数组,右半部分为循环有序数组
        if ls[mid] >= ls[low]:
            if ls[low] <= target < ls[mid]:
                high = mid-1
            else:
                low = mid+1
        # 若右半部分为有序数组,左半部分为循环有序数组
        else:
            if ls[mid] < target <= ls[high]:
                low = mid+1
            else:
                high = mid-1
    return -1
ls = [12,16,18,20,41,100,1,4,6,9]
target = 90
print(find(ls, target))

同类型题目:旋转数组的最小数

将求最小数转化为求旋转数组的转折点,一个循环有序数组从中间切分,一定分成一个有序数组和一个循环有序数组,转折点一定在无序的那边。

class Solution:
    def minNumberInRotateArray(self, rotateArray):
        # write code here
        if not rotateArray:
            return 0
        low = 0
        high = len(rotateArray) - 1
        while rotateArray[low] >= rotateArray[high]:
            if high - low <= 1:
                return rotateArray[high]
            mid = (high+low)//2
            if rotateArray[high] == rotateArray[low] and rotateArray[mid] == rotateArray[low]:
                return self.normal_find(rotateArray)
            #左边为有序,在右边寻找转折点(最小数)
            if rotateArray[mid] >= rotateArray[low]:
                low = mid
            # 右边为有序,在左边寻找转折点(最小数)
            elif rotateArray[mid] <= rotateArray[high]:
                high = mid
                
    def normal_find(self, rotateArray):
                # write code here
        if len(rotateArray) == 0:
            return 0
        for i in range(1,len(rotateArray)-1):
            if rotateArray[i-1] <= rotateArray[i] <= rotateArray[i+1]:
                pass
            else:
                return rotateArray[i+1]

 

<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、付费专栏及课程。

余额充值