题目描述
思路解析
一句话答案:LeetCode 704 二分查找:升序数组里找 target,靠有序每次砍掉一半——比中点后大了收右、小了收左,范围收空就返回 -1,时间 O(log n)、空间 O(1)。
有序数组里找一个 target,返回什么
给一个升序(从小到大排好)、无重复数字的数组 nums 和目标值 target:在数组里就返回下标,不在返回 -1。题面例子 nums=[-1,0,3,5,9,12],找 9 返回下标 4,找 2 返回 -1。题目还卡死 O(log n),不许一个个挨着找。
一个个往后比,为什么撑不到 O(log n)
笨办法是从头扫到尾,一个个和 target 比。数组有 n 个数,最坏(target 排末尾或不存在)要比满 n 次,这就是 O(n)(大 O 记号,衡量数据规模增大时操作量的放大倍数)。n 到百万就得比百万次,O(log n) 把这条路堵死了。
凭什么敢一次丢掉一半数不看
钥匙全在『有序』二字上。看正中间的 nums[mid](mid 是中点下标)和 target 比:nums[mid] 小于 target,左边只会更小、够不着,左半连 mid 一起扔掉;大于 target 则右半作废。一比砍掉一半,剩下的才是 target 藏身处,这就是二分查找——每次砍掉一半候选的找法。乱序里左右没规律,砍哪半都可能误伤 target。
看中点该往哪半收,闭区间怎么不越界
用两个指针 l、r 圈住没排除的范围,起初 l=0、r=n-1,两端都算数,这叫闭区间 [l,r]。每轮取中点 mid=l+(r-l)//2 和 target 比:相等直接返回 mid;小于 target,l 跳到 mid+1;大于 target,r 退到 mid-1。
这里藏着一条循环不变量(每一轮都成立的保证):target 若存在,就一定还在 [l,r] 内。收缩必须跨过 mid——比过的 mid 已排除,l 或 r 落到 mid 下一格,范围才真变小。循环条件写 while l<=r,因为 l==r 时闭区间里还剩一格没查。
[-1,0,3,5,9,12] 里找 9、找 2 各几轮
先找 9。l=0、r=5,mid=0+(5-0)//2=2,nums[2]=3 比 9 小,l 跳到 3。第二轮 l=3、r=5,mid=3+(5-3)//2=4,nums[4]=9 相等,返回下标 4,两轮命中。
再找 2。l=0、r=5,mid=2,nums[2]=3 比 2 大,r 退到 1。第二轮 l=0、r=1,mid=0,nums[0]=-1 比 2 小,l 跳到 1。第三轮 l=1、r=1,mid=1,nums[1]=0 比 2 小,l 跳到 2,此时 l 超过 r,范围空了,返回 -1。
缩不动的死循环,卡在 mid 没跨过去
收缩那步最阴:把 l=mid+1 写成 l=mid,比过的 mid 没跨过去,l 卡着不动就成了缩不动的死循环。循环条件也别写成 while l<r,闭区间里 l==r 那一格会被漏掉。mid 写成 (l+r)//2 看着没差,可数组一大 l+r 会超整数上限溢出,得写 l+(r-l)//2 绕开。
复杂度上,每比一次范围减半,n 个数最多 log₂n 次收敛,时间 O(log n);只用 l、r、mid 三个变量,空间 O(1)。百万个数约 20 次比较,比 O(n) 快几个数量级。
▶ 动画逐步走查(文字版)——想跟着上方动画一帧帧对照就展开
记住这条「看中间:小了丢左半、大了丢右半、范围每次减半」,下面每一帧都在套它。
范围用 l 和 r 圈定,灰掉的格子是已经被排除、再也不用看的部分。
看正中间 nums[5]=12,比 9 大。有序数组里它右边只会更大,所以 9 只可能在它左边。
把右半(含中点 5)整段灰掉排除,r 跳到 4。一次砍掉一半,范围缩到 [0, 4]。
看正中间 nums[2]=3,比 9 小。有序数组里它左边只会更小,所以 9 只可能在它右边。
把左半(含中点 2)整段灰掉排除,l 跳到 3。一次砍掉一半,范围缩到 [3, 4]。
看正中间 nums[3]=5,比 9 小。有序数组里它左边只会更小,所以 9 只可能在它右边。
把左半(含中点 3)整段灰掉排除,l 跳到 4。一次砍掉一半,范围缩到 [4, 4]。
范围正中间 nums[4]=9 正好等于 9,命中!返回下标 4。
命中的格子绿色高亮。11 个数最多比 4 次(log₂11≈3.5),这就是 O(log n) 的威力。
找不到的情形同样靠砍半收敛:当 l 超过 r、范围空了,就返回 -1。
中间 nums[5]=12 比 2 大,目标若存在只能在左半。
灰掉右半,r → 4,范围缩到 [0, 4]。
中间 nums[2]=3 比 2 大,目标若存在只能在左半。
灰掉右半,r → 1,范围缩到 [0, 1]。
中间 nums[0]=-1 比 2 小,目标若存在只能在右半。
灰掉左半,l → 1,范围缩到 [1, 1]。
中间 nums[1]=0 比 2 小,目标若存在只能在右半。
收缩后 l=2 超过了 r=1,范围空了——说明 2 根本不存在,返回 -1。
所有格子都被排除、范围空掉,确认没有这个数。找不到也只用 O(log n) 次比较。
目标偏大时,每次都丢掉左半,范围像滚雪球一样向右收紧。
看正中间 nums[5]=12,比 33 小。有序数组里它左边只会更小,所以 33 只可能在它右边。
把左半(含中点 5)整段灰掉排除,l 跳到 6。一次砍掉一半,范围缩到 [6, 10]。
看正中间 nums[8]=23,比 33 小。有序数组里它左边只会更小,所以 33 只可能在它右边。
把左半(含中点 8)整段灰掉排除,l 跳到 9。一次砍掉一半,范围缩到 [9, 10]。
看正中间 nums[9]=28,比 33 小。有序数组里它左边只会更小,所以 33 只可能在它右边。
把左半(含中点 9)整段灰掉排除,l 跳到 10。一次砍掉一半,范围缩到 [10, 10]。
范围正中间 nums[10]=33 正好等于 33,命中!返回下标 10。
命中的格子绿色高亮。11 个数最多比 4 次(log₂11≈3.5),这就是 O(log n) 的威力。
边界先想清:空数组、单元素、目标超出两端,二分都能自然收敛到 -1。
两个高频追问:有序是前提、防溢出的中点写法。
参考代码
def search(nums, target): l, r = 0, len(nums) - 1 while l <= r: mid = l + (r - l) // 2 # 防溢出 if nums[mid] == target: return mid elif nums[mid] < target: l = mid + 1 # 丢左半 else: r = mid - 1 # 丢右半 return -1复杂度
- 时间:O(log n),每比一次范围减半,n 个数最多 log₂n 次
- 空间:O(1),只用 l、r、mid 三个变量
易错点
面试追问把动画讲成自己的话
追问为什么二分查找要求数组有序?
追问mid 为什么写成 l + (r - l) / 2?
这道题到这就讲完了。动画和文字是同一套思路——别光看,关掉页面自己默写一遍。然后顺着主线继续:
搜索二维矩阵
LeetCode 74 · 中等 · 沿着 二分查找 继续往下推进
把这道题真正学会,再走
图解算法年卡 ¥99 /年
- ✓本题每步动画的吴师兄语音讲解(全站陆续覆盖)
- ✓小欧带 8 步通关训练——追问到不看答案也能写对、能 30 秒讲给面试官听
- ✓学习报告,记录每道题的掌握程度
76k+ GitHub Star · 吴师兄开源图解算法,几十万开发者在看的算法讲解
想成体系刷透这类套路?去图解算法专题