算法题学习---寻找峰值

271 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第20天,点击查看活动详情

题目

描述

给定一个长度为n的数组nums,请你找到峰值并返回其索引。数组可能包含多个峰值,在这种情况下,返回任何一个所在位置即可。

1.峰值元素是指其值严格大于左右相邻值的元素。严格大于即不能有等于

2.假设 nums[-1] = nums[n] = −∞

3.对于所有有效的 i 都有 nums[i] != nums[i + 1]

4.你可以使用O(logN)的时间复杂度实现此问题吗?

数据范围:

1≤nums.length≤2×10^5

−2^31<=nums[i]<=2^31−1

如输入[2,4,1,2,7,8,4]时,会形成两个山峰,一个是索引为1,峰值为4的山峰,另一个是索引为5,峰值为8的山峰,如下图所示:

示例1

输入:
[2,4,1,2,7,8,4]
返回值:
1
说明:
48都是峰值元素,返回4的索引1或者8的索引5都可以     

示例2

输入:
[1,2,3,1]
返回值:
2
说明:
3 是峰值元素,返回其索引 2    

分析

分析特殊情况

因为已经限定了输入数组的最小长度为1,所以如果数组长度为1,说明index为0位置的数据就是山峰,直接返回即可。

分析一般情况

遍历法

最容易想到的方法,从左到右进行遍历,比较当前元素和下一个元素,如果比下一个元素小,那么继续寻找,如果比下一个元素大,说明这是第一个山峰,返回索引即可,如下:

        for(int i = 0; i < nums.size(); ++i)
        {
            if(nums[i] > nums[i+1] || i == nums.size() - 1)
            {
                return i;
            }
        }
 
        return 0;

明显地,这个方法的最坏情况,即,只有一个山峰,且是最后一个元素时,我们需要遍历所有的元素,时间复杂度为O(n),不符合题意。

二分法

要求达到O(logn)的复杂度,一般就是需要将问题进行分治的,我们参考二分查找的问题,先找到中点,然后比较中点与目标值的大小,再进一步缩小要查找的范围,直到左右边界相遇。

在这个问题中,我们并不是要查找一个目标值,而是而是要查找一个峰值,假如我们现在使用中点处的值将数组分为了左右两个部分,那么,我们要思考,哪一个部分会出现峰值呢?

实际上我们可以通过中点mid处和mid+1处值的大小得知这个位置是上坡还是下坡:

  • 如果nums[mid] < nums[mid + 1],说明此时是上坡,那么我们往右半区间查找可以找到峰值;
  • 如果nums[mid] > nums[mid + 1],说明此时是下坡,那么左半区间一定有峰值。

按照这样的逻辑更新左右区间,直到两个区间相遇,这时,我们就找到了峰值。

代码示例

注意,我们更新区间时,begin更新为mid+1,因为我们知道它比mid大,中点不需要再比较了;

更新end为mid,因为我们并不知道mid-1和它的关系,它有可能是前半部分的峰值。

class Solution {
public:
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param nums int整型vector 
     * @return int整型
     */
    int findPeakElement(vector<int>& nums) {
        // write code here
        if(nums.size() == 1)
        {
            return 0;
        }

        int begin = 0;
        int end = nums.size() - 1;

        while(begin < end)
        {
            int mid = (begin + end) / 2;
            if(nums[mid] > nums[mid + 1])
            {
                end = mid;
            }
            else if(nums[mid] < nums[mid + 1])
            {
                begin = mid + 1;
            }
        }
        return begin;
    }
};