算法题分享 | 寻找峰值

62 阅读4分钟

此去经年,眉目成书🌋_4_蘅皋暮_来自小红书网页版.jpg

题目

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

给你一个整数数组 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]

题解

解题思路

这道题如何以 O(n) 的时间复杂度进行解决的话,那么是很简单的。例如我们可以直接找到数组中的最大值即可,因为最大值一定是峰值。

但是题目中明确指定了必须实现时间复杂度为 O(log n) 的算法来解决此问题,而且,如果直接 O(n) 解决,那么这道题也就出得没有意义啦。

爬坡法
实际上我们可以使用爬坡的方法进行解决,最开始可以随机选一个下标为 idx 的元素,对于该元素,按以下步骤进行处理:

  1. 如果 nums[idx] 刚好就是峰值,直接返回;

  2. 如果 nums[idx] < nums[idx],那么就向右爬坡(右侧一定存在峰值),idx = idx + 1;

  3. 否则,nums[idx] < nums[idx - 1],那么就向左爬坡(左侧一定存在峰值),idx = idx - 1。

如此迭代,最终一定能找到峰值。

但是当前算法实际上时间复杂度还是 O(n),最坏情况是数组是有序的,这样就得爬坡到最后一个元素。所以还得想办法将时间复杂度降到 O(log n)。

可以知道,当处于 idx 时,一旦决定向一个方向爬坡后,接下来就只会向该方向继续爬坡直到找到峰值,是不可能在往反方向走的,根据这一特点,很容易就能想到这里可以使用二分查找进行优化。

二分查找优化
二分查找时使用 left、right、idx 分别指向查找范围左边界、右边界、正在处理元素。 每次二分,按以下步骤处理:

  1. 如果 nums[idx] 刚好就是峰值,直接返回;

  2. 如果 nums[idx] < nums[idx],那么就缩小范围到右侧,left = idx + 1;

  3. 否则,nums[idx] < nums[idx - 1],那么就缩小范围到左侧,right = idx - 1。

由于峰值是一定是存在的,所以在查找过程中,最终一定会在步骤 1 处返回。

代码

class Solution {
    public int findPeakElement(int[] nums) {
        
        int n = nums.length;
        int left = 0;
        int right = n - 1;
        
        while(left <= right) {

            int idx = left + (right - left) / 2;

            int res1 = compare(nums, idx, idx - 1);
            int res2 = compare(nums, idx, idx + 1);

            if (res1 > 0 && res2 > 0) {
                return idx;
            }   

            if (res2 < 0) {
                left = idx + 1;
            } else {
                right = idx - 1;
            }
        }

        // 这里只是为了不报错,实际上一定会在 while 查找过程中找到并且返回。
        return -1;
    }

    // 用于比较数组中指定两值的大小
    // 支持执行 -1 或 n,根据题目说明,-1 或 n 下标对应的元素值当做无穷小。
    public int compare(int[] nums, int i, int j) {
        int n = nums.length;
        if (isOutOfBounds(n, i) && !isOutOfBounds(n, j)) {
            return -1;
        }

        if (!isOutOfBounds(n, i) && isOutOfBounds(n, j)) {
            return 1;
        }

        if (isOutOfBounds(n, i) && isOutOfBounds(n, j)) {
            return 0;
        }

        return nums[i] > nums[j] ? 1 : -1; 
    }

    // 判断下标是否是 -1 或 n
    public boolean isOutOfBounds(int n, int i) {
        if (i < 0 || i == n) {
            return true;
        }

        return false;
    }
}

复杂度分析

  • 时间复杂度:O(log n)
    n 为数组的长度,每次二分会砍掉一半范围。
  • 空间复杂度:O(1)

优质项目推荐

推荐一个可用于练手、毕业设计参考、增加简历亮点的项目。

lemon-puls/txing-oj-backend: Txing 在线编程学习平台,集在线做题、编程竞赛、即时通讯、文章创作、视频教程、技术论坛为一体

公众号

有兴趣可以关注公众号一起学习更多的干货哈!

扫码_搜索联合传播样式-白色版.png