题目描述
思路解析
一句话答案:LeetCode 162 寻找峰值:峰值指比左右邻居都高的位置,返回任意一个峰下标。比 nums[mid] 与 nums[mid+1],上坡令 l=mid+1、下坡令 r=mid,朝高一侧收缩必撞峰。时间 O(log n)、空间 O(1)。
寻找峰值到底要我返回哪个下标
给一个数组 nums,峰值指严格大于左右两个邻居的位置,两端之外看作负无穷,题目保证相邻不相等。要返回任意一个峰值下标,可能有多个峰,返回哪个都对。题面 nums=[1,3,5,7,9,11,13,15,17,16,14,12,10] 在下标 8 处的 17 比左右都高,返回 8。因为两端是负无穷,哪怕数组一路涨,末位也算峰,峰必存在。
从头扫一遍要 O(n),凭什么能更快
线性做法一眼能想到:从左往右扫,逐个查谁比左右都高,撞到第一个就返回。它对,但题目说能在 O(log n) 内完成,扫一遍是线性的、太亏。数组根本没排序、忽高忽低,而二分查找通常要求有序,凭什么把 O(n) 压成 O(log n)?
数组无序,凭什么还能对半砍
能砍的底气来自一个爬坡直觉。看任意下标和它右邻居谁高:右邻居更高就是上坡(此刻在往上走),顺上坡往右,坡不能无限升,右端外是负无穷,早晚掉头,那个由升转降的转折点就是峰。所以朝更高的一侧走,前方必有峰;右邻居更矮则是下坡,峰在左侧,当前位置也可能就是峰顶。每比一次就能定下峰在哪半边、扔掉另一半。二分要的不是「有序」,而是「哪半边一定有解」,本题用负无穷的边界凑齐了它。
拿 mid 和右邻居比,往哪半边收
左指针 l 从 0、右指针 r 从 n-1 起,区间 [l, r]。每轮取正中下标 mid=(l+r)//2,比 nums[mid] 和右邻居 nums[mid+1]:前者小是上坡,峰在右,左边界推到 l=mid+1,mid 矮当不了峰一并丢;否则是下坡,峰在左半段且可能就是 mid,右边界收成 r=mid、保留 mid。统一比右邻居还躲开越界:l<r 时 mid 最多 r-1,mid+1 不越界。收到 l==r 缩成一点,那下标就是峰值。
示例这串数,l 和 r 怎么夹到下标 8
就拿题面这串,n=13。l=0、r=12:mid=6,nums[6]=13 小于 nums[7]=15,上坡,l=7。l=7、r=12:mid=9,nums[9]=16 大于 nums[10]=14,下坡,r=9。l=7、r=9:mid=8,nums[8]=17 大于 nums[9]=16,下坡,r=8。l=7、r=8:mid=7,nums[7]=15 小于 nums[8]=17,上坡,l=8。l==r==8,返回 8,即 nums[8]=17。
收反方向,峰值当场从眼皮底下漏掉
最刺眼的坑是把收缩方向反了:上坡本该往右,若当成峰在左、朝更矮一侧收,峰当场从眼皮底下漏掉。循环开闭也易写坏,得写 while l<r 配 r=mid:若写成 l<=r,收缩到 l==r 时循环不停,又取一次 mid==l、下坡分支再令 r=mid,两指针原地不动,卡成死循环。还有 mid 写成 (l+r) 再除,l、r 很大时会溢出,改 l+(r-l)//2 先求差再折半就不会爆。整个过程每轮把区间砍一半,最多 log n 轮收敛到一点,时间 O(log n);只用 l、r、mid 三个变量,空间 O(1)。
▶ 动画逐步走查(文字版)——想跟着上方动画一帧帧对照就展开
思路一句话:比 mid 和 mid+1,上坡 l=mid+1,下坡 r=mid,每次砍一半,撞上即峰值。下面一步步演给你看。
先记住:数组左右两端之外都是负无穷。这保证从任何位置往「更高」的方向走,最终都能撞到一个峰,不会走出界。
高亮这段是一路上坡。上坡不可能无限升(右端是负无穷),途中必出现一个比右邻高的转折点,那就是峰值。二分正是利用这一点。
初始时左指针 l 指向下标 0,右指针 r 指向最右端下标 12。峰值一定在这段区间里,开始对半收窄。
本轮的搜索区间是高亮这段 [0, 12]。峰值只可能在这里面,区间外已经被前面的判断排除掉了。
取区间正中间 mid = (l + r) / 2 = 6。接下来要拿它和右邻居比,判断该往哪半边收。
把 mid 的值 13 和右邻居 nums[7] = 15 比一比,看 mid 是在上坡还是下坡。
nums[mid] 比右邻居小,是上坡,更高的峰在右侧。丢掉 mid 及左边(变灰那段),左指针跳到 l=7。
本轮的搜索区间是高亮这段 [7, 12]。峰值只可能在这里面,区间外已经被前面的判断排除掉了。
取区间正中间 mid = (l + r) / 2 = 9。接下来要拿它和右邻居比,判断该往哪半边收。
把 mid 的值 16 和右邻居 nums[10] = 14 比一比,看 mid 是在上坡还是下坡。
nums[mid] 比右邻居大,是下坡或就是峰顶,峰值在 mid 这一侧(含 mid 自己)。丢掉 mid 右边(变灰那段),右指针收到 r=9。
本轮的搜索区间是高亮这段 [7, 9]。峰值只可能在这里面,区间外已经被前面的判断排除掉了。
取区间正中间 mid = (l + r) / 2 = 8。接下来要拿它和右邻居比,判断该往哪半边收。
把 mid 的值 17 和右邻居 nums[9] = 16 比一比,看 mid 是在上坡还是下坡。
nums[mid] 比右邻居大,是下坡或就是峰顶,峰值在 mid 这一侧(含 mid 自己)。丢掉 mid 右边(变灰那段),右指针收到 r=8。
本轮的搜索区间是高亮这段 [7, 8]。峰值只可能在这里面,区间外已经被前面的判断排除掉了。
取区间正中间 mid = (l + r) / 2 = 7。接下来要拿它和右邻居比,判断该往哪半边收。
把 mid 的值 15 和右邻居 nums[8] = 17 比一比,看 mid 是在上坡还是下坡。
nums[mid] 比右邻居小,是上坡,更高的峰在右侧。丢掉 mid 及左边(变灰那段),左指针跳到 l=8。
左右指针相遇在下标 8,区间收成一个点。这里的 nums[8] = 17 比左右邻居都大,就是要找的峰值。
三个高频追问:无序也能二分的本质、多峰返回哪个、相邻不等条件的作用。
参考代码
def findPeakElement(nums): l, r = 0, len(nums) - 1 # 搜索区间 [l, r] while l < r: mid = (l + r) // 2 if nums[mid] < nums[mid + 1]: # 上坡,峰在右 l = mid + 1 else: # 下坡,峰在左(含 mid) r = mid return l # l == r 即峰值下标复杂度
- 时间:O(log n),每轮把搜索区间砍掉一半,最多 log n 轮就收敛到一个点
- 空间:O(1),只用 l、r、mid 三个下标变量,不开额外数组
易错点
面试追问把动画讲成自己的话
追问数组无序,为什么还能用二分?
追问有多个峰值时返回哪个?
追问为什么相邻元素不相等这个条件很重要?
这道题到这就讲完了。动画和文字是同一套思路——别光看,关掉页面自己默写一遍。然后顺着主线继续:
H 指数 II
LeetCode 275 · 中等 · 沿着 二分套路 继续往下推进
把这道题真正学会,再走
图解算法年卡 ¥99 /年
- ✓本题每步动画的吴师兄语音讲解(全站陆续覆盖)
- ✓小欧带 8 步通关训练——追问到不看答案也能写对、能 30 秒讲给面试官听
- ✓学习报告,记录每道题的掌握程度
76k+ GitHub Star · 吴师兄开源图解算法,几十万开发者在看的算法讲解
想成体系刷透这类套路?去图解算法专题