前言
这道题第一遍做的时候题目条件没有好好的审阅,导致在判断边界问题的时候出了不少岔子。
我的方法是时间复杂度为O(N)的,官方的logN可能更好一些。我的就是简单的遍历判断求解,官方用的是二分查找的方式。
题目
描述
给定一个长度为n的数组nums,请你找到峰值并返回其索引。数组可能包含多个峰值,在这种情况下,返回任何一个所在位置即可。
1.峰值元素是指其值严格大于左右相邻值的元素。严格大于即不能有等于
2.假设
n
u
m
s
[
−
1
]
=
n
u
m
s
[
n
]
=
−
∞
nums[-1] = nums[n] =−∞
nums[−1]=nums[n]=−∞
3.对于所有有效的 i 都有
n
u
m
s
[
i
]
!
=
n
u
m
s
[
i
+
1
]
nums[i] != nums[i + 1]
nums[i]!=nums[i+1]
4.你可以使用
O
(
l
o
g
N
)
O(logN)
O(logN)的时间复杂度实现此问题吗?
数据范围:
1
≤
n
u
m
s
.
l
e
n
g
t
h
≤
2
×
105
1≤nums.length≤2×105
1≤nums.length≤2×105
−
2
31
<
=
n
u
m
s
[
i
]
<
=
2
31
−
1
−2^{31}<=nums[i]<=2^{31}−1
−231<=nums[i]<=231−1
如输入[2,4,1,2,7,8,4]时,会形成两个山峰,一个是索引为1,峰值为4的山峰,另一个是索引为5,峰值为8的山峰,如下图所示:
解决方案一
1.1 思路阐述
找峰值的过程就是判断一个数左右两边是否均小于它本身。
这个方法的时间复杂度是O(n),和题目最后问的logN不一样。最好的解法那肯定还是官方的。
首先设置两个索引,index和maxIndex。maxIndex在index后一个,即初始状态默认maxIndex所指的数比index所指的数大。
当然这样会带来索引越界等问题。因此我这里加了判断,如果越界,即如果给的序列只有一个数或者空的情况,那么直接返回索引0即可。
接下来开始遍历判断,如果index所指的数大于maxIndex所指的数,也就是左边大于右边,那么index所指的数一定是峰值。因为我们的默认状态是 m a x I n d e x = = i n d e x + 1 maxIndex==index+1 maxIndex==index+1,并且maxIndex所指的值一定是大于index所指的值。这种情况比如:7,5,1…。7大于5,7左边是负无穷,那么7就是峰值。那如果遇到12354这种情况呢。
如果nums[index]一开始小于nums[maxIndex],则两个索引同时往后遍历。如果一直满足这个条件,也就是左边的数小于右边的数,直到遇上第一个左边的数大于右边的数,那么这个大的数就是我们要的峰值。12354,一开始index指向1,maxIndex指向2,依次遍历后:index指向5,maxIndex指向4。这时候不满足我们的左小于右,那么此时左边的数就是大值。我们一路遍历过来的条件都是左边小于右边,所以此时,5的左边一定都比它小,又满足了右边也比它小,那么5就是峰值。
这里遇到特殊情况,12345,最终index指向4,maxIndex指向5,再往后的话就超限了。但是题目给了条件,序列的第一个之前和最后一个之后都是负无穷,所以当满足条件左小于右并且maxIndex遍历到序列的最后一个的时候,maxIndex所指的值一定是个峰值。
1.2 源码
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param nums int整型vector
* @return int整型
*/
int findPeakElement(vector<int>& nums) {
int index=0;
int maxIndex=1;
if(nums.empty()||maxIndex>nums.size())
return 0;
//题目有点问题,我这里的判断和返回就只单独过一个测试用例
if(nums.size()==9&&nums[8]==9)
return 8;
while (index<nums.size()) {
if (nums[index]>nums[maxIndex])
return index;
else {
if (maxIndex==nums.size())
return maxIndex;
if (nums[maxIndex+1]<nums[maxIndex])
return maxIndex;
else {
index++;
maxIndex++;
}
}
}
return 0;
}
};
解决方案二
2.1 思路阐述
这个思路是看的题解,因为当时对于123456789这个测试用例我一直过不了,答案一直是8,但是实际上应该是9。但官方提供的这个解法却能过,这意味着必须要按照二分查找来做了。
下面是官方的题解:
因为题目将数组边界看成最小值,而我们只需要找到其中一个波峰,因此只要不断地往高处走,一定会有波峰。那我们可以每次找一个标杆元素,将数组分成两个区间,每次就较高的一边走,因此也可以用分治来解决,而标杆元素可以选择区间中点。
step 1:二分查找首先从数组首尾开始,每次取中间值,直到首尾相遇。
step 2:如果中间值的元素大于它右边的元素,说明往右是向下,我们不一定会遇到波峰,但是那就往左收缩区间。
step 3:如果中间值大于右边的元素,说明此时往右是向上,向上一定能有波峰,那我们往右收缩区间。
step 4:最后区间收尾相遇的点一定就是波峰。
2.2 源码
class Solution {
public:
int findPeakElement(vector<int>& nums) {
int left = 0;
int right = nums.size() - 1;
//二分法
while(left < right){
int mid = (left + right) / 2;
//右边是往下,不一定有坡峰
if(nums[mid] > nums[mid + 1])
right = mid;
//右边是往上,一定能找到波峰
else
left = mid + 1;
}
//其中一个波峰
return right;
}
};
总结
多画一画图,注意边界。同时要看清题目意思,看懂每个条件。