这个题目主要是弄懂峰值出现的时候会有哪些情况,事情就会简单很多。所以遇到题的时候,还是要冷静思考问题的关键所在。
首先我们要注意到问题中的一个特点:你可以假设nums[-1] = nums[n] = -∞
,所以两边的肯定都是极小值。那我们可以从nums[0]开始向后遍历,会出现3种情况:
- 数组一直是递减的。如下图所示:那么此时的峰值就是Num是[0]。
- 数组一直是递增的,那么峰值就是
nums[nums.length - 1]
。
- 数组是不规则的。通过前面其实我们能得出一条规律或者寻找的方法:我们沿着数组递增的方向寻找,直到遇到一个数字它的后面是递减的,那么这个值就是峰值。因为我们保证了前面的序列都是递增的,也就是此元素肯定大于前面的值,然后又在后面遇到一个比它小的值,那么它就一定是峰值了。比如情况1,那么第一个就是等着;情况2,最后一个是峰值;那这种不规则的情况也就是这样找的。如图所示
1. 循环遍历
那么问题就变得简单了,我们只需要按照情况3里的思路遍历数组,直到找到符合条件的元素为止。代码如下:
public class Solution {
public int findPeakElement(int[] nums) {
for (int i = 0; i < nums.length - 1; i++) {
if (nums[i] > nums[i + 1])
return i;
}
return nums.length - 1;
}
}
2. 二分查找
当然题目还要求时间复杂度为 O(logN)的,所以一定想到了二分查找。我们把一个数组看成是交替的升序和降序序列,每次我们找到一个mid,然后判断它在哪种序列中(通过与右边的元素比较)。如果它在升序的序列中(nums[mid] < nums[mid+1]),那么一定存在一个峰值是在mid的右边的,因为我们有个假设条件nums[-1] = nums[n] = -∞
,这样我们就可以把寻找空间缩小到它的右边;如果它在降序的序列中(nums[mid] > nums[mid+1]),那么一定存在一个峰值可能就在它的左边(当然包括它自己,因为它本身比较大),同样也是因为刚才那个假设条件。直到这个区间缩小到为1时,也就找到了那个元素。二分查找有递归和非递归两种,分别如下:
//递归解法
public class Solution {
public int findPeakElement(int[] nums) {
return search(nums, 0, nums.length - 1);
}
public int search(int[] nums, int l, int r) {
if (l == r)
return l;
int mid = (l + r) / 2;
if (nums[mid] > nums[mid + 1]) //它是在一个下降区间,则找它的左半边
return search(nums, l, mid);
return search(nums, mid + 1, r); //否则在上升区间,找它的右半边
}
}
//非递归解法
public class Solution {
public int findPeakElement(int[] nums) {
int l = 0, r = nums.length - 1;
while (l < r) {
int mid = (l + r) / 2;
if (nums[mid] > nums[mid + 1])
r = mid;
else
l = mid + 1;
}
return l;
}
}