寻找峰值 图解题解
这道题到底在问什么
- 输入
- nums = [1,3,5,7,9,11,13,15,17,16,14,12,10]
- 输出
- 8(nums[8]=17 比左右都大)
最优解:为什么这么做
一句话答案: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)。
▶ 动画逐步走查(共 21 步)——想跟着动画一帧帧对照就展开
- 3思路一句话:比 mid 和 mid+1,上坡 l=mid+1,下坡 r=mid,每次砍一半,撞上即峰值。下面一步步演给你看。
- 4先记住:数组左右两端之外都是负无穷。这保证从任何位置往「更高」的方向走,最终都能撞到一个峰,不会走出界。
- 5高亮这段是一路上坡。上坡不可能无限升(右端是负无穷),途中必出现一个比右邻高的转折点,那就是峰值。二分正是利用这一点。
- 6初始时左指针 l 指向下标 0,右指针 r 指向最右端下标 12。峰值一定在这段区间里,开始对半收窄。
- 7本轮的搜索区间是高亮这段 [0, 12]。峰值只可能在这里面,区间外已经被前面的判断排除掉了。
- 8取区间正中间 mid = (l + r) / 2 = 6。接下来要拿它和右邻居比,判断该往哪半边收。
- 9把 mid 的值 13 和右邻居 nums[7] = 15 比一比,看 mid 是在上坡还是下坡。
- 10nums[mid] 比右邻居小,是上坡,更高的峰在右侧。丢掉 mid 及左边(变灰那段),左指针跳到 l=7。
- 11本轮的搜索区间是高亮这段 [7, 12]。峰值只可能在这里面,区间外已经被前面的判断排除掉了。
- 12取区间正中间 mid = (l + r) / 2 = 9。接下来要拿它和右邻居比,判断该往哪半边收。
- 13把 mid 的值 16 和右邻居 nums[10] = 14 比一比,看 mid 是在上坡还是下坡。
- 14nums[mid] 比右邻居大,是下坡或就是峰顶,峰值在 mid 这一侧(含 mid 自己)。丢掉 mid 右边(变灰那段),右指针收到 r=9。
- 15本轮的搜索区间是高亮这段 [7, 9]。峰值只可能在这里面,区间外已经被前面的判断排除掉了。
- 16取区间正中间 mid = (l + r) / 2 = 8。接下来要拿它和右邻居比,判断该往哪半边收。
- 17把 mid 的值 17 和右邻居 nums[9] = 16 比一比,看 mid 是在上坡还是下坡。
- 18nums[mid] 比右邻居大,是下坡或就是峰顶,峰值在 mid 这一侧(含 mid 自己)。丢掉 mid 右边(变灰那段),右指针收到 r=8。
- 19本轮的搜索区间是高亮这段 [7, 8]。峰值只可能在这里面,区间外已经被前面的判断排除掉了。
- 20取区间正中间 mid = (l + r) / 2 = 7。接下来要拿它和右邻居比,判断该往哪半边收。
- 21把 mid 的值 15 和右邻居 nums[8] = 17 比一比,看 mid 是在上坡还是下坡。
- 22nums[mid] 比右邻居小,是上坡,更高的峰在右侧。丢掉 mid 及左边(变灰那段),左指针跳到 l=8。
- 23左右指针相遇在下标 8,区间收成一个点。这里的 nums[8] = 17 比左右邻居都大,就是要找的峰值。
⚠️ 容易写错的地方
✗ 错:比较 nums[mid] 和 nums[mid-1],又忘了处理 mid=0 越界
✓ 对:统一比较 nums[mid] 和 nums[mid+1],配合 r=mid 永不越界
while l<r 保证 mid<r,所以 mid+1 一定在界内,写法最稳
✗ 错:上坡时写成 r=mid,把方向搞反
✓ 对:上坡(右邻更高)往右走 l=mid+1
右邻更高说明更高的峰在右,必须丢掉左半边
✗ 错:循环条件写成 l<=r 且收敛时还动 r
✓ 对:循环条件 l<r,相遇即停返回 l
l<=r 配 r=mid 时 mid 可能恒等于 l,会死循环
完整代码(Python / C++ / Java)
Python
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 即峰值下标C++
int findPeakElement(vector<int>& nums){
int l = 0, r = nums.size() - 1;
while (l < r) {
int mid = (l + r) / 2;
if (nums[mid] < nums[mid + 1]) l = mid + 1;
else r = mid;
}
return l;
}Java
public int findPeakElement(int[] nums) {
int l = 0, r = nums.length - 1;
while (l < r) {
int mid = (l + r) / 2;
if (nums[mid] < nums[mid + 1]) l = mid + 1;
else r = mid;
}
return l;
}复杂度
时间
O(log n)
每轮把搜索区间砍掉一半,最多 log n 轮就收敛到一个点
空间
O(1)
只用 l、r、mid 三个下标变量,不开额外数组
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 寻找峰值 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
数组没排序,二分不是要求有序吗,这里凭什么能用?+
二分真正依赖的不是「有序」,而是每一步都能判定「解一定落在哪半边」。本题靠比较 nums[mid] 和 nums[mid+1]:上坡则右侧必有峰(坡撞到负无穷边界前一定掉头),下坡则左侧含 mid 必有峰。每轮都能锁定半边、扔掉另一半,这个「可判定的收缩」才是二分的本钱;排序只是获得它的一种常见途径,而非唯一途径。
数组里有多个峰值,这个二分会返回哪一个?+
不确定,取决于每轮 mid 落在哪、往哪边收,最终停在某一个峰上。题目只要求返回「任意一个」峰值下标,所以返回哪个都算对,不必纠结是不是最左或最高的那个。若想固定返回某个特定的峰,得改判定规则,但本题没这个要求。
题目「相邻元素不相等」这个条件去掉会怎样?+
会破坏收缩的确定性。比较 nums[mid] 和 nums[mid+1] 时若两者相等,就分不清该往左还是往右,两边都可能有峰、也可能没有,二分失去「哪半边一定有解」的依据,O(log n) 的解法不再成立,只能退回线性扫描或加更复杂的处理。相邻不等,正是让每一步比较都有明确方向的前提。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄把 寻找峰值 一步步讲透(全站已上线 905 份讲透,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
把这道题真正拿下
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。