题目描述
升序数组中找 target 的起始和结束下标,不存在返回 [−1, −1]。
思路解析
先二分找到任意一个 8,再向左、向右一格格线性扩到边。可如果数组全是 8,向两边扩会退化成 O(n),等于白做了二分。题目给了有序,应该全程都用二分。
转折:把「找边界」改写成「找第一个满足某条件的位置」。不变量:数组有序,所以「≥8 的位置」是连续一段,第一个 ≥8 就是左界 3;「大于8 的位置」也是连续一段,第一个大于8 是 5,它前一位 4 就是右界。复用同一个下界二分两次,各 O(log n)。
找左界 · 准备 [0,5]:第一趟找左界,目标是「第一个 ≥ 8 的下标」。l=0、r=5,圈住整个数组。
找左界 · 第 1 轮取 mid:中点 mid=2,nums[2] = 7。判断它满不满足「≥ 8」。
找左界 · 判断(不满足·负例):7 比 8 小,不满足 ≥8。那 mid 以及它左边都不可能是左界,左界只能在 mid 右边。
找左界 · 收缩:下标 0、1、2(含 mid)全比 8 小,整段灰掉,l 跳到 mid+1 = 3。范围缩到 [3, 5]。
找左界 · 第 2 轮取 mid:新范围 [3, 5],中点 mid=4,nums[4] = 8。再判断「≥ 8」。
找左界 · 判断(满足):8 满足 ≥8,mid 可能就是左界,但它前面也许还有更早的 8。所以不能丢 mid,收右 r=mid=4,继续往左看。范围 [3, 4]。
找左界 · 第 3 轮收敛:范围 [3, 4],mid=3,nums[3]=8 也满足 ≥8,r 收到 3。现在 l == r == 3,收敛——这就是第一个 8,左界 = 3。
找右界 · 第一个大于 8:第二趟找右界。技巧:找「第一个大于 8 的下标」,它前一位就是最后一个 8。l、r 重新圈回整个数组。
找右界 · mid=2 → 收右:mid=2 是 7,不满足「大于8」,第一个大于8 在右边,l 跳到 3。范围 [3, 5]。
找右界 · mid=4 → 仍收右:mid=4 还是 8,等于不算大于,仍不满足,l 跳到 5。下标 3、4(两个 8)也灰掉。范围只剩 [5, 5]。
找右界 · 收敛 → 答案:l==r==5,nums[5]=10 是第一个大于 8 的位置。它前一位 4 就是最后一个 8。配上左界 3,最终答案 [3, 4]。
「某值的左右边界 / 出现次数」都能化成两次下界二分:第一个 ≥t 给左界,第一个 大于t 给右界+1,两者相减就是出现次数。
参考代码(Python)
1def searchRange(self, nums, target):
2 def lower(t): # 第一个 >= t 的位置
3 l, r = 0, len(nums) # 开区间右端取 n
4 while l < r:
5 mid = (l + r) // 2
6 if nums[mid] >= t: r = mid # 满足,收右保留候选
7 else: l = mid + 1 # 不满足,往右
8 return l
9 lo = lower(target)
10 if lo == len(nums) or nums[lo] != target:
11 return [-1, -1] # 没找到该值
12 return [lo, lower(target + 1) - 1] # 右界 = 第一个 大于 t 再减1复杂度分析
- 时间复杂度:O(log n) —— 两次下界二分,每次都把区间砍半
- 空间复杂度:O(1) —— 只用 l、r、mid 几个指针,没开额外结构
套路模板
记住骨架:写一个 lower(t),左界=lower(t)、右界=lower(t+1)−1、出现次数=lower(t+1)−lower(t)。一个函数解决一类边界题。
1# 下界二分:第一个 >= t 的位置
2def lower(t):
3 l, r = 0, n
4 while l < r:
5 mid = (l + r) // 2
6 if nums[mid] >= t: r = mid
7 else: l = mid + 1
8 return l
9left, right = lower(t), lower(t + 1) - 1 # [左界, 右界]易错点
- 错误写法:直接返回 lower(t),不判存在 → 正确写法:先看 lo<n 且 nums[lo]==target(lower(t) 只给「应插入位置」,target 不存在时它指向第一个比 t 大的数,不验证会把它当答案)
- 错误写法:右界单独写一套找最后位置的二分 → 正确写法:复用 lower(t+1) − 1(另写一套容易把 <=/< 和 +1/-1 搞反;用 lower(t+1)−1 只维护一份逻辑,不易错)
以上文字与上方动画完全同一思路:先看动画建立直觉 → 读文字巩固 → 关掉页面自己默写一遍代码,能写出来才算真的掌握。