mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4mobile wallpaper 5mobile wallpaper 6
929 字
2 分钟
二分搜索:寻找峰值
2026-05-06

题目概览#

范围#

来源内容
class006FindPeakElement

题型归纳#

题型: 二分找局部最优

特征: 要找任意一个峰值,且相邻元素不相等。

解法: 根据 mid 左右的上升、下降趋势判断哪一侧一定存在峰值。

易错点: 边界位置要单独判断,避免访问越界。

峰值不要求全局最大,只要找到一个局部极大值就行——这是二分能介入的关键信号。两端各有一个隐含的 -∞ 边界,保证了峰值一定存在。先单独判断首尾,剩下的安全区间 [1, n-2] 才交给二分,每次根据 mid 左右的斜率方向缩小范围;最容易出错的是直接从 [0, n-1] 开始二分,循环体内访问 arr[m-1]arr[m+1] 时越界。

例题:162. 寻找峰值#

题目链接

题意#

峰值元素是指其值严格大于左右相邻值的元素。

给你一个整数数组 nums,找到峰值元素并返回其索引。数组可能包含多个峰值,在这种情况下,返回 任何一个峰值 所在位置即可。

你可以假设 nums[-1] = nums[n] = -∞

你必须实现时间复杂度为 O(log n) 的算法来解决此问题。

示例#

示例 1:

输入: nums = [1,2,3,1]

输出: 2

解释: 3 是峰值元素,你的函数应该返回其索引 2。

示例 2:

输入: nums = [1,2,1,3,5,6,4]

输出: 1 或 5

解释: 你的函数可以返回索引 1,其峰值元素为 2;或者返回索引 5,其峰值元素为 6。

提示#

  • 1 <= nums.length <= 1000
  • -2^31 <= nums[i] <= 2^31 - 1
  • 对于所有有效的 i 都有 nums[i] != nums[i + 1]

解法#

核心思路#

把数组看成一段连续的山坡。因为题目默认 nums[-1] = nums[n] = -∞,并且相邻元素不相等,所以数组中一定存在峰值。

先单独判断两个边界:

  • 如果 arr[0] > arr[1],那么 0 位置就是峰值。
  • 如果 arr[n - 1] > arr[n - 2],那么 n - 1 位置就是峰值。

排除边界后,峰值一定在 [1, n - 2] 范围内。对中点 m 做判断:

  • 如果 arr[m - 1] > arr[m],说明左侧存在上坡,往左找。
  • 如果 arr[m] < arr[m + 1],说明右侧存在上坡,往右找。
  • 否则 arr[m] 同时大于左右相邻值,m 就是峰值。

沿任意上升方向走,一定能到达峰值——因为数组两端被 -∞ 封住,上坡不可能无限延伸。

根据 mid 左右斜率判断峰值方向

边界与安全区间划分示意

核心模板#

public static int findPeakElement(int[] arr) {
int n = arr.length;
if (n == 1) {
return 0;
}
if (arr[0] > arr[1]) {
return 0;
}
if (arr[n - 1] > arr[n - 2]) {
return n - 1;
}
int l = 1, r = n - 2, m = 0, ans = -1;
while (l <= r) {
m = (l + r) / 2;
if (arr[m - 1] > arr[m]) {
r = m - 1;
} else if (arr[m] < arr[m + 1]) {
l = m + 1;
} else {
ans = m;
break;
}
}
return ans;
}

变量角色#

lr 是二分的左右边界,初始锁定在安全区间 [1, n-2] 内;m 是每轮的中点;ans 记录找到的峰值下标,初始为 -1,找到后立即跳出循环。

过程拆解#

  1. 特判:长度为 1 时直接返回 0
  2. 首尾判断:检查 arr[0]arr[n-1] 是否为峰值,是则提前返回。
  3. 二分:在安全区间 [1, n-2] 内取中点 m,比较 arr[m-1]arr[m]arr[m+1] 三者关系。
  4. 收边界:左侧下降则往左收,右侧上升则往右收,两侧都不满足则 m 即为峰值。

易错点#

二分区间的边界陷阱

错误写法:直接从 [0, n-1] 开始二分,循环体内同时访问 arr[m-1]arr[m+1]

int l = 0, r = n - 1;
while (l <= r) {
m = (l + r) / 2;
if (arr[m - 1] > arr[m]) { // m == 0 时,arr[-1] 越界!
r = m - 1;
} else if (arr[m] < arr[m + 1]) { // m == n-1 时,arr[n] 越界!
l = m + 1;
} else {
return m;
}
}

为什么错:m 可能取到 0n-1,访问 arr[-1]arr[n] 时直接抛出 ArrayIndexOutOfBoundsException

正确写法一:先处理首尾,再从 [1, n-2] 开始二分(即本文模板)。

正确写法二:换成只比较 arr[m]arr[m+1] 的写法,配合 while (l < r)——此时 m 永远小于 rm+1 不会越界:

public int findPeakElement(int[] nums) {
int l = 0, r = nums.length - 1;
while (l < r) {
int m = l + (r - l) / 2;
if (nums[m] > nums[m + 1]) {
r = m;
} else {
l = m + 1;
}
}
return l;
}
口诀

先判首尾,再在 [1, n-2] 内二分,靠斜率方向收边界。

复杂度#

  • 时间复杂度:O(log n)
  • 空间复杂度:O(1)
分享

如果这篇文章对你有帮助,欢迎分享给更多人!

二分搜索:寻找峰值
https://github.com/algorithmzuo/algorithm-journey/tree/main/src/class006
作者
黎明
发布于
2026-05-06
许可协议
MIT

部分信息可能已经过时

目录