力扣解题-162. 寻找峰值

5 阅读7分钟

力扣解题-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)要求的解法。

核心逻辑拆解

寻找峰值的基础逻辑:

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

总结

  1. 线性遍历验证法(第一次解答):O(n)时间+O(1)空间,逻辑直观但未满足题目O(logn)要求;
  2. 迭代二分查找法:O(logn)时间+O(1)空间,最优解,满足题目要求且效率最高;
  3. 递归二分查找法:O(logn)时间+O(logn)空间,递归实现,适合理解分治思想;
  4. 关键技巧
    • 核心思想:利用“上坡必到峰值”的规律,二分查找只需比较mid和mid+1,无需验证完整峰值条件;
    • 收敛保证:题目假设nums[-1]/nums[n]=-∞,且元素不相等,因此二分最终必收敛到峰值;
    • 性能选择:工程中优先选迭代二分法(空间最优),学习阶段可通过递归版理解逻辑。