大家好啊!今天我们来聊一个面试中经常出现的数据结构问题------有序数组中的Upper Bound。
什么是Upper Bound?
简单来说,给定一个已排序 的数组和一个目标值target,我们要找到数组中第一个大于 target的元素的索引。如果所有元素都小于等于target,则返回数组的长度(也就是数组末尾的下一个位置)。
举个例子:
- 数组:
[2, 3, 7, 10, 11, 11, 25] - target = 9 → 输出 3(因为
arr[3] = 10 > 9) - target = 11 → 输出 6(因为
arr[6] = 25 > 11) - target = 100 → 输出 7(所有元素都小于等于100,返回数组长度)
方法一:线性搜索(O(n))
最直观的方法就是从头到尾遍历,找到第一个大于target的元素。
python
def upperBound(arr, target):
n = len(arr)
for i in range(n):
if arr[i] > target:
return i
return n
虽然简单,但效率不高,时间复杂度为O(n)。
很多同学学完二分查找基础后,卡在Upper Bound这种边界变化上,光靠看代码很难真正理解指针移动的瞬间。
推荐试试一个叫图码的宝藏网站,它能用交互式动画把60多种算法 跑给你看,甚至支持输入自己的数据或上传C/C++/Java/Python代码来生成可视化,非常适合408考研 和数据结构期末考试 的复习场景。
现在就去体验下,边看动画边理解Upper Bound的执行过程,比死啃书本快多了。
图码-数据结构与算法交互式可视化平台
访问网站:https://totuma.cn
方法二:二分查找(O(log n))
既然数组是有序的,我们当然可以用二分查找来优化!
思路:
- 初始化
lo = 0,hi = n-1,res = n(默认返回数组长度) - 计算
mid = (lo + hi) // 2 - 如果
arr[mid] > target,说明mid可能是候选答案,更新res = mid,然后在左半部分继续查找(因为要找第一个大于target的元素) - 如果
arr[mid] <= target,说明答案一定在右半部分
python
def upperBound(arr, target):
lo, hi = 0, len(arr) - 1
res = len(arr)
while lo <= hi:
mid = lo + (hi - lo) // 2
if arr[mid] > target:
res = mid
hi = mid - 1
else:
lo = mid + 1
return res
为什么这样是对的?
- 当
arr[mid] > target时,mid是一个有效上界,但左边可能还有更小的索引满足条件,所以继续在左边找 - 当
arr[mid] <= target时,mid及其左边都不可能是答案,所以去右边找
方法三:用内置函数(一行搞定)
很多语言都提供了现成的函数:
Python :使用bisect模块
python
import bisect
def upperBound(arr, target):
return bisect.bisect_right(arr, target)
C++ :使用std::upper_bound
cpp
#include <algorithm>
int index = upper_bound(arr.begin(), arr.end(), target) - arr.begin();
总结
| 方法 | 时间复杂度 | 空间复杂度 |
|---|---|---|
| 线性搜索 | O(n) | O(1) |
| 二分查找 | O(log n) | O(1) |
| 内置函数 | O(log n) | O(1) |
面试小贴士:
- 如果面试官让你实现Upper Bound,记得用二分查找,不要写成线性搜索
- 注意边界条件:当所有元素都小于等于target时,返回数组长度
- 这个方法在搜索插入位置 、区间查找等问题中也非常有用
学会了的小伙伴快去试试吧!有任何问题欢迎评论区留言讨论~