leetcode
leetcode 151 ~ 200
寻找峰值

寻找峰值

难度:

标签:

题目描述

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

给你一个整数数组 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
  • -231 <= nums[i] <= 231 - 1
  • 对于所有有效的 i 都有 nums[i] != nums[i + 1]

代码结果

运行时间: 16 ms, 内存: 16.0 MB


/*
 * 思路:
 * 使用二分查找算法来解决问题,但是因为二分查找的过程不适合用 Java Stream 实现,
 * 所以我们直接使用标准的二分查找方法。
 */
 
public class Solution {
    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, left, mid);
        } else {
            return binarySearch(nums, mid + 1, right);
        }
    }
}

解释

方法:

这个题解使用二分查找的方法来寻找峰值。由于题目要求时间复杂度为O(log n),普通的线性扫描不符合要求。我们可以利用二分查找的思想,每次取中点,比较中点与其右侧相邻元素的大小关系,如果中点较大,说明左侧一定存在峰值,可以缩小范围到左半部分;如果中点较小,说明峰值一定在右半部分,可以缩小范围到右半部分。不断迭代直到左右边界相遇,此时找到了一个峰值。

时间复杂度:

O(log n)

空间复杂度:

O(1)

代码细节讲解

🦆
为什么在二分查找的每次比较中,只比较中点与其右侧相邻元素,而不是同时比较左侧元素?
在二分查找中,比较中点与其右侧相邻元素的原因在于简化决策过程。如果中点元素大于右侧元素,我们可以确定至少存在一个峰值在当前中点或其左侧,因为要么当前中点就是峰值,要么在中点左侧由于数组开头边界的原因存在一个递增到达峰值后递减的情况。若中点元素小于右侧元素,则至少存在一个峰值在右侧,因为从中点元素到右侧元素是递增的,必然会达到一个峰值。这样的决策保证了每次操作都能有效缩小搜索范围的一半,同时维持算法的逻辑简洁性。
🦆
在二分查找过程中,如果中点和右侧元素相等,该如何处理?题目中提到nums[i] != nums[i+1],这种情况会出现吗?
题目的前提条件是nums[i] != nums[i+1],即数组中任意相邻元素都不相等。因此,在实际的题设中,中点和其右侧元素相等的情况不会出现。这个条件简化了问题,避免了处理相等情况的复杂性,使得每次比较都可以明确决定搜索的方向,从而直接支持二分查找的决策过程。
🦆
题解中提到如果中点大于其右侧元素,峰值一定在左半部分。能否详细解释为什么这样判断峰值的位置是正确的?
如果中点大于其右侧的元素,这意味着从中点到中点右侧元素存在一个下坡。根据题目的设定,数组的两端可以被视为负无穷,这样在中点左侧要么存在一个上坡后下坡的峰值结构,要么中点本身就是峰值。此时,无需考虑右侧部分,因为即使右侧存在峰值,左侧的峰值更容易通过当前的二分查找路径被找到。这种判断确保了在满足中点大于右侧元素的条件下,左半部分至少存在一个峰值,从而可以安全地缩小搜索范围以加快查找速度。
🦆
在最坏情况下,二分查找需要多少次迭代才能找到峰值?这是否总是等于log_2(n)?
在最坏情况下,二分查找每次迭代都会将搜索范围缩小到原来的一半。因此,从长度为n的数组开始,需要迭代log_2(n)次才能将搜索范围缩小到只剩一个元素,即找到峰值。这里的对数是以2为底的,因为每次操作都是将区间分为两部分。因此,这是一个典型的对数时间复杂性例子,符合题目要求的O(log n)时间复杂度。

相关问题

山脉数组的峰顶索引

符合下列属性的数组 arr 称为 山脉数组
  • arr.length >= 3
  • 存在 i0 < i < arr.length - 1)使得:
    • arr[0] < arr[1] < ... arr[i-1] < arr[i]
    • arr[i] > arr[i+1] > ... > arr[arr.length - 1]

给你由整数组成的山脉数组 arr ,返回满足 arr[0] < arr[1] < ... arr[i - 1] < arr[i] > arr[i + 1] > ... > arr[arr.length - 1] 的下标 i

你必须设计并实现时间复杂度为 O(log(n)) 的解决方案。

 

示例 1:

输入:arr = [0,1,0]
输出:1

示例 2:

输入:arr = [0,2,1,0]
输出:1

示例 3:

输入:arr = [0,10,5,2]
输出:1

 

提示:

  • 3 <= arr.length <= 105
  • 0 <= arr[i] <= 106
  • 题目数据保证 arr 是一个山脉数组