题目描述
在升序数组里找 target,返回它的下标;找不到返回 -1。
思路解析
最直接:从下标 0 挨个比,碰到 9 就返回。可这样要扫到下标 4,6 个数比 5 次;一百万个数最坏比一百万次。题目都把有序明牌摆你面前了,却还从头扫,等于白白浪费了这个条件。
转折:因为数组有序,正中间那个数就是一道分水岭。用 l、r 圈出范围,mid 取正中间——这是不变量:mid 处的数比 target 小,target 只可能在它右边;比 target 大,只可能在左边。所以每比一次就能整段丢掉一半,不用回头扫。
准备 · 闭区间 [0,5]:左指针 l=0,右指针 r=5,范围就是整个数组。要找的 target = 9。
第 1 轮 · 取 mid:中间下标 mid = (0+5)//2 = 2,nums[2] = 5。拿它和目标 9 比一比。
第 1 轮 · 判断(偏小,丢左半):5 比 9 小。因为右边全都更大,9 只可能在 mid 右边,mid 这个 5 本身也不是答案——左半连 mid 一起,整段可以扔了。
第 1 轮 · 收缩:把左指针跳到 mid+1 = 3,下标 0、1、2(含那个 5)整段灰掉丢弃。范围缩到 [3, 5],只剩一半。
第 2 轮 · 取 mid:新范围 [3, 5],新的中间 mid = (3+5)//2 = 4,nums[4] = 9。再和目标 9 比。
第 2 轮 · 命中!:nums[4] 正好等于 9,命中!返回下标 4。从 6 个数里只比了 2 次就找到——这就是砍半的威力。
再看找不到 · 准备:换个例子:找 6。数组里根本没有 6,看二分怎么干净地收尾、返回 -1。
mid=2 · 偏小收左:mid=2 处是 5,比 6 小,丢左半,l 跳到 3。范围 [3, 5]。
mid=4 · 偏大收右:mid=4 处是 9,比 6 大,这回丢右半,r 收到 mid−1 = 3。现在 l 和 r 都指向 3。
mid=3 · 偏大,区间清空:mid=3 处是 7,比 6 大,r 收到 2。这下 l(3) 越过了 r(2),范围空了,循环结束——全程没碰到 6,返回 -1。
只要数据有序(或答案有单调性),就该想二分:搜索插入位置、x 的平方根、爱吃香蕉的珂珂,全是它的变形。
参考代码(Python)
1def search(self, nums, target):
2 l, r = 0, len(nums) - 1 # 闭区间 [l, r]
3 while l <= r: # 注意带等号
4 mid = l + (r - l) // 2 # 取中间,防溢出
5 if nums[mid] == target:
6 return mid # 命中,直接返回下标
7 elif nums[mid] < target:
8 l = mid + 1 # 偏小,丢左半
9 else:
10 r = mid - 1 # 偏大,丢右半
11 return -1 # 区间空了还没找到复杂度分析
- 时间复杂度:O(log n) —— 每轮把搜索范围砍掉一半,n→n/2→n/4…
- 空间复杂度:O(1) —— 只用 l、r、mid 三个指针,没开额外数组
套路模板
三条铁律:while 带等号、mid 防溢出、边界 +1/−1。背下来基本不会错。
1l, r = 0, len(nums) - 1
2while l <= r:
3 mid = l + (r - l) // 2
4 if nums[mid] == target: return mid
5 elif nums[mid] < target: l = mid + 1
6 else: r = mid - 1
7return -1易错点
- 错误写法:while l < r(漏等号) → 正确写法:while l <= r(闭区间下区间剩一个元素时 l==r,漏等号会跳过它,该元素正好是答案就找不到)
- 错误写法:l = mid 或 r = mid(不动) → 正确写法:l = mid+1 / r = mid-1(mid 已比过、不是答案,不 +1/-1 会卡在原地,区间不缩小,死循环)
以上文字与上方动画完全同一思路:先看动画建立直觉 → 读文字巩固 → 关掉页面自己默写一遍代码,能写出来才算真的掌握。