查找元素的首尾位置:两次二分
首位置 = 第一个 ≥ target;尾位置 = 第一个 > target 减一。
用两次二分:第一次找 lower_bound(第一个 ≥ target 的下标),第二次找 upper_bound(第一个 > target 的下标)。若 lower_bound 处不是 target 或超出数组,说明不存在,返回 [−1,−1];否则返回 [lower, upper−1]。O(log n)。
给定升序数组 nums 和目标值 target,返回 target 在数组中的开始和结束位置;不存在返回 [−1,−1]。
示例:nums = [5,7,7,8,8,10],target = 8 → [3,4]。
一次二分只能找到一个位置——首和尾需要两种不同的二分。
首尾位置 = 两个边界
target 可能出现多次。它的「首位置」是第一个等于 target 的位置,即 lower_bound。
它的「尾位置」是最后一个等于 target 的位置,等于 upper_bound(第一个 > target 的位置)减一。
所以问题变成两个独立的二分:找 lower_bound 和 upper_bound。
边界思维区间 [lower, upper−1] 正好框住所有等于 target 的元素——用开区间/闭区间的边界定界。
第一次二分:找 lower_bound
lower_bound = 第一个满足 nums[i] ≥ target 的下标。
规则:nums[mid] < target → left = mid+1(排除);否则 right = mid−1。
nums = [5,7,7,8,8,10],target = 8:
mid=2:7 < 8 → left=3。
mid=4:8 ≥ 8 → right=3。
mid=3:8 ≥ 8 → right=2。
left=3 > right=2,lower_bound = 3。
第二次二分:找 upper_bound
upper_bound = 第一个满足 nums[i] > target 的下标。
规则:nums[mid] ≤ target → left = mid+1;否则 right = mid−1。
nums = [5,7,7,8,8,10],target = 8:
mid=2:7 ≤ 8 → left=3。
mid=4:8 ≤ 8 → left=5。
mid=5:10 > 8 → right=4。
left=5 > right=4,upper_bound = 5。
答案 = [3, 5−1] = [3,4]。
不存在的情况
如果 lower_bound 越界,或者 nums[lower_bound] ≠ target,说明 target 不存在,返回 [−1,−1]。
举例:target = 6,lower_bound 会停在第一个 ≥ 6 的位置(下标 1 的值 7),nums[1] = 7 ≠ 6 → 不存在。
这个判空条件很重要:两次二分本身不会告诉你「不存在」。
Go:lower_bound + upper_bound
func lowerBound(nums []int, t int) int {l, r := 0, len(nums)-1for l <= r {m := l + (r-l)/2if nums[m] < t { l = m + 1 } else { r = m - 1 }}return l}func searchRange(nums []int, target int) []int {lo := lowerBound(nums, target)if lo == len(nums) || nums[lo] != target { return []int{-1, -1} }return []int{lo, lowerBound(nums, target+1) - 1}}
1lower_bound 找第一个 ≥ target。
2小于 target 排除左半边。
3upper_bound 复用同一函数:找第一个 ≥ target+1,即第一个 > target。
4判空:越界或值不等 → 不存在。
总结
lower_bound 与 upper_bound 两次二分,框出 target 的全部位置。
- 首位置 = 第一个 ≥ target,尾位置 = 第一个 > target 减一。
- 两种边界只有一处判断不同:< 还是 ≤。
- 判空靠「值不等于 target 或越界」,二分本身不报不存在。