力扣解题-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³¹ <= nums[i] <= 2³¹ - 1
对于所有有效的 i 都有 nums[i] != nums[i + 1]
Related Topics
数组、二分查找
第一次解答
解题思路
核心方法:线性遍历验证法,从数组第二个元素(索引1)开始遍历,逐个验证当前元素是否满足“严格大于左右相邻元素”的峰值条件,若遍历结束未找到则返回索引0(数组第一个元素,因题目假设nums[-1]=-∞,单元素数组或递增数组的第一个元素天然为峰值),时间复杂度O(n)、空间复杂度O(1),是直观但未满足题目O(logn)要求的解法。
核心逻辑拆解
寻找峰值的基础逻辑:
- 峰值定义:
- 中间元素:严格大于左右两个相邻元素;
- 边界元素:数组第一个元素只需严格大于第二个元素(nums[-1]=-∞),最后一个元素只需严格大于倒数第二个元素(nums[n]=-∞);
- 遍历验证:
- 从索引1开始遍历到数组末尾,逐个检查当前元素是否为峰值;
- 处理最后一个元素的边界情况(无右侧元素,只需大于左侧);
- 若遍历完未找到峰值,说明数组是严格递增的,返回索引0(第一个元素为峰值)。
具体执行逻辑
- 初始化变量:
length:数组最后一个元素的索引(nums.length-1);begin:遍历起始索引,初始为1;
- 遍历验证(
begin <= length):- 获取当前元素的左邻居
left = nums[begin-1]、当前值cur = nums[begin]; - 边界判断(最后一个元素):若
begin+1 > length,只需验证cur > left,满足则返回begin; - 中间元素:获取右邻居
right = nums[begin+1],若cur > left 且 cur > right,返回begin; - 不满足则
begin++继续遍历;
- 获取当前元素的左邻居
- 返回默认值:遍历结束未找到峰值,返回0(适配严格递增数组,如[1,2,3,4],索引0为峰值)。
执行流程可视化(以示例1 nums=[1,2,3,1]为例)
| 遍历索引begin | left | cur | right | 验证条件 | 结果 |
|---|---|---|---|---|---|
| 1 | 1 | 2 | 3 | 2>1但2<3 | 不满足,继续 |
| 2 | 2 | 3 | 1 | 3>2且3>1 | 满足,返回2 |
关键细节说明
- 边界处理:单独判断最后一个元素的场景(无右侧元素),仅需比较左侧;
- 默认返回0:适配两种场景:
- 数组长度为1(如nums=[5]),直接返回0;
- 数组严格递增(如nums=[1,2,3,4]),索引0满足“nums[-1]=-∞ < nums[0]”,是峰值;
- 题目条件利用:题目明确nums[i]≠nums[i+1],无需处理相等的情况。
性能说明
- 时间复杂度:O(n)(最坏情况遍历整个数组,如严格递增/递减数组);
- 空间复杂度:O(1)(仅使用常数级额外变量);
- 优势:
- 逻辑直观,逐一遍历验证,新手易理解和实现;
- 代码简洁,无复杂的二分逻辑;
- 劣势:
- 未满足题目“时间复杂度O(logn)”的要求;
- 大数据量下效率低于二分查找。
public int findPeakElement(int[] nums) {
int length=nums.length-1;
int begin=1;
while(begin<=length){
int left=nums[begin-1];
int cur=nums[begin];
if(begin+1>length){
if(cur>left)
return begin;
}else {
int right=nums[begin+1];
if(cur>left&&cur>right){
return begin;
}
}
begin++;
}
return 0;
}
示例解答
解题思路
解法1:二分查找法(最优解,O(logn))
核心方法:利用“数组元素不相等”和“nums[-1]/nums[n]=-∞”的特性,通过二分查找向“上坡”方向移动,最终定位到峰值,时间复杂度O(logn),完全满足题目要求,是本题的最优解法。
代码实现
public int findPeakElement(int[] nums) {
int left = 0;
int right = nums.length - 1;
while (left < right) {
int mid = left + (right - left) / 2;
// 比较mid和mid+1,判断上坡方向
if (nums[mid] < nums[mid + 1]) {
// 上坡向右,峰值在右半区
left = mid + 1;
} else {
// 下坡向左,峰值在左半区(包含mid)
right = mid;
}
}
// 循环结束时left==right,即为峰值索引
return left;
}
核心逻辑说明
- 核心规律:
- 若
nums[mid] < nums[mid+1]:说明mid处于“上坡”,峰值一定在mid右侧(因为数组末尾是-∞,上坡最终会到顶形成峰值); - 若
nums[mid] > nums[mid+1]:说明mid处于“下坡”,峰值一定在mid左侧(或mid本身就是峰值);
- 若
- 二分规则:
- 循环条件
left < right,保证最终收敛到一个索引; - 上坡时
left = mid + 1(排除mid,向右找); - 下坡时
right = mid(保留mid,向左找);
- 循环条件
- 结果收敛:最终
left == right,该索引一定是峰值(因题目保证至少存在一个峰值)。
执行流程可视化(以示例2 nums=[1,2,1,3,5,6,4]为例)
| 循环次数 | left | right | mid | nums[mid] | nums[mid+1] | 比较结果 | 指针更新 | 说明 |
|---|---|---|---|---|---|---|---|---|
| 1 | 0 | 6 | 3 | 3 | 5 | 3<5 | left=4 | 上坡向右 |
| 2 | 4 | 6 | 5 | 6 | 4 | 6>4 | right=5 | 下坡向左 |
| 3 | 4 | 5 | 4 | 5 | 6 | 5<6 | left=5 | 上坡向右 |
| 循环结束 | 5 | 5 | - | - | - | - | - | 返回5(峰值索引) |
性能说明
- 时间复杂度:O(logn)(每次循环缩小一半搜索范围);
- 空间复杂度:O(1)(仅使用常数级额外变量);
- 优势:
- 满足题目O(logn)时间复杂度要求,效率远高于线性遍历;
- 代码简洁,核心逻辑仅需比较mid和mid+1;
- 无需处理复杂的边界条件,天然收敛到峰值。
解法2:递归二分法(O(logn),递归实现)
核心方法:将迭代二分改为递归实现,逻辑与迭代版一致,适合理解二分查找的递归思想。
代码实现
public int findPeakElement(int[] nums) {
return binarySearch(nums, 0, nums.length - 1);
}
private int binarySearch(int[] nums, int left, int right) {
if (left == right) {
return left;
}
int mid = left + (right - left) / 2;
if (nums[mid] < nums[mid + 1]) {
// 上坡,递归右半区
return binarySearch(nums, mid + 1, right);
} else {
// 下坡,递归左半区
return binarySearch(nums, left, mid);
}
}
核心逻辑说明
- 递归终止条件:
left == right,返回该索引; - 递归分支:
- 上坡:递归处理
[mid+1, right]; - 下坡:递归处理
[left, mid];
- 上坡:递归处理
- 结果一致性:与迭代版二分法结果完全一致。
性能说明
- 时间复杂度:O(logn)(递归深度为logn);
- 空间复杂度:O(logn)(递归栈深度);
- 优势:
- 递归逻辑更贴合二分查找的“分治”思想;
- 代码结构清晰,易于理解;
- 劣势:递归栈会占用额外空间,数据量大时可能栈溢出(本题数组长度≤1000,无此问题)。
总结
- 线性遍历验证法(第一次解答):O(n)时间+O(1)空间,逻辑直观但未满足题目O(logn)要求;
- 迭代二分查找法:O(logn)时间+O(1)空间,最优解,满足题目要求且效率最高;
- 递归二分查找法:O(logn)时间+O(logn)空间,递归实现,适合理解分治思想;
- 关键技巧:
- 核心思想:利用“上坡必到峰值”的规律,二分查找只需比较mid和mid+1,无需验证完整峰值条件;
- 收敛保证:题目假设nums[-1]/nums[n]=-∞,且元素不相等,因此二分最终必收敛到峰值;
- 性能选择:工程中优先选迭代二分法(空间最优),学习阶段可通过递归版理解逻辑。