开启掘金成长之旅!这是我参与「掘金日新计划 · 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
说明:
4和8都是峰值元素,返回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;
}
};