二分查找返回第一次查到的位置和最后一次查到的位置

本文介绍了一种在有序数组中查找特定目标元素首次和末次出现位置的算法实现。通过两个函数`left_bound`和`right_bound`,可以精确地找到目标元素的起始和结束下标,适用于二分搜索场景。

第一次查到的位置: 

def left_bound(nums,target):
        low = 0
        high = len(nums)-1
        while low<=high:
            mid = low+(high-low)//2
            if nums[mid]<target:
                low = mid+1
            elif nums[mid]>target:
                high = mid-1
            else:
                high = mid-1 #向右缩小范围
        if low>=len(nums) or nums[low]!=target:
            return -1
        return low

最后一次查到的位置:

 def right_bound(nums,target):
        low = 0
        high = len(nums)-1
        while low<=high:
            mid = low+(high-low)//2
            if nums[mid]<target:
                low = mid+1
            elif nums[mid]>target:
                high = mid-1
            else:
                low = mid+1
        if high<0 or nums[high]!=target:
            return -1
        return high

 

这是一个非常好的优化思路!你问的是: > **“第二次查找右边界时,能不能从第一次找到的左边界位置开始查找?”** 我们来详细分析这个问题。 --- ### ✅ 简短回答: **可以,但对时间复杂度提升有限;更关键的是——不能只在 `[first, first]` 范围内查,仍需继续二分到右侧。** 也就是说: 👉 你可以让第二次二分查找的 `left = first`(因为右边界一定 ≥ 左边界),但这只是个小优化,**本质还是要在整个右半部分中二分找最大索引**。 --- ### ✅ 为什么不能“直接从 first 开始线性扫描”? 虽然你知道右边界 ≥ `first`,但如果用线性方式向右扩展(如原题中的 while 循环): ```python while ans[1] < len(nums) and nums[ans[1]] == target: ans[1] += 1 ``` 这会导致最坏情况下时间复杂度退化为 **O(n)**(比如全是 target)。 而我们的目标是保持 **O(log n)**。 所以即使你知道起点是 `first`,也**必须使用二分法在 `[first, n-1]` 区间内查找最后一个 target**。 --- ### ✅ 优化版代码:利用 first 作为 left 起点 ```python class Solution: def searchRange(self, nums: List[int], target: int) -> List[int]: def findFirst(): left, right = 0, len(nums) - 1 index = -1 while left <= right: mid = (left + right) // 2 if nums[mid] == target: index = mid right = mid - 1 # 继续向左 elif nums[mid] < target: left = mid + 1 else: right = mid - 1 return index def findLast(first): # 右边界一定 >= first,所以可以从 first 开始 left = first right = len(nums) - 1 index = first while left <= right: mid = (left + right) // 2 if nums[mid] == target: index = mid left = mid + 1 # 继续向右找更大的位置 else: right = mid - 1 return index first = findFirst() if first == -1: return [-1, -1] last = findLast(first) return [first, last] ``` --- ### ✅ 解释说明: - `findLast(first)` 中设置 `left = first` 是合理且安全的,因为: - 如果存在多个 `target`,右边界一定在 `[first, n-1]` 范围内。 - 这样缩小了搜索空间,**虽然仍是 O(log n),但常数因子略小一点**。 - 关键点:依然使用**二分法向右推进**,而不是线性扫描,保证复杂度不退化。 --- ### ✅ 实际性能影响举例: | 情况 | 数组长度 | target 占比 | 是否启用 `left=first` | |------|----------|-------------|------------------------| | 极端情况 | 1e6 | 全是 target | 减少约一半比较次数 | | 一般情况 | 1e5 | 少量重复 | 提升很小 | | 边界情况 | 找不到 | first=-1 | 不执行第二次 | ➡️ 所以这个优化属于“锦上添花”,不是本质改进。 --- ### ✅ 更进一步:是否能用一次二分解决? 理论上不行。因为你需要两个独立信息: - 最左边的位置 - 最右边的位置 除非结合额外数据结构(如哈希表预处理),否则至少需要两次二分或一次遍历。 --- ### ✅ 总结 | 问题 | 回答 | |------|------| | 第二次能否从 first 开始? | ✅ 可以,设 `left = first` 是合法且轻微优化 | | 能否省略第二次二分? | ❌ 不行,必须确认右边界 | | 能否用线性扫描代替第二次二分? | ❌ 会退化为 O(n),破坏算法效率 | | 最佳实践? | 两次二分,第二次 `left = first` 作为起点 | ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值