题目描述:峰值元素指的是其值严格大于左右相邻的元素。给定一个整数数组,其可能包含多个峰值元素,返回任意一个峰值元素的下标。(可认为nums[-1] = nums[n] = -∞
)
要求解法达到
l
o
g
(
n
)
log(n)
log(n)时间复杂度。
简单想法就是遍历一次,对每个元素判断是否是峰值即可。但是时间复杂度为 O ( n ) O(n) O(n)。要想达到 l o g ( n ) log(n) log(n)的复杂度,只能采用类似二分的思想,每次排除掉一半的元素才行。接下来就想怎么二分呢。
我们先随便找一个位置,这个位置的情况只可能是如下4种:
- 左右两侧的数都比它小
- 左右两侧的数都比它大
- 左右两侧的数一大一小(左小右大)
- 左右两侧的数一大一小(左大右小)
- 对于1,则已经找到峰值,直接返回。
- 对于2,峰值可能出现在左侧,也可能出现在右侧(左右两侧都一定存在峰值)
- 对于3,峰值可能出现在左侧,也可能出现在右侧(左侧不一定存在峰值,但右侧一定存在,极端情况是左到右单调递增,则最后一个位置为峰值,因为可认为
nums[n]
为负无穷) - 对于4,峰值可能出现在左侧,也可能出现在右侧(右侧不一定存在峰值,但左侧一定存在,极端情况是左到右单调递减,则第一个位置为峰值,因为可认为
nums[-1]
为负无穷)
由于只需要找到一个峰值即可,那么只需要进行二分,每次往一定存在峰值的一端走即可(往更高处走)
我自己的代码(加了太多边界特判了,后来发现其实并不需要)
class Solution {
public int findPeakElement(int[] nums) {
return find(nums, 0, nums.length - 1);
}
private int find(int[] nums, int l, int r) {
if (l > r) return -1;
if (l == r) {
if (l == 0 && l == nums.length - 1) return l;
if (l == 0) return nums[0] > nums[1] ? 0 : -1;
if (l == nums.length - 1) return nums[l] > nums[l - 1] ? l : -1;
return nums[l] > nums[l - 1] && nums[l] > nums[l + 1] ? l : -1;
}
int mid = l + r >> 1;
if (mid == 0) {
if (nums[0] < nums[1]) return find(nums, mid + 1, r);
else return 0;
}
if (mid == nums.length - 1) {
if (nums[mid] < nums[mid - 1]) return find(nums, l, mid - 1);
else return mid;
}
// 4种情况
if (nums[mid] > nums[mid - 1] && nums[mid] > nums[mid + 1]) return mid;
if (nums[mid] < nums[mid - 1] && nums[mid] < nums[mid + 1]) return find(nums, mid + 1, r);
if (nums[mid] > nums[mid - 1] && nums[mid] < nums[mid + 1]) return find(nums, mid + 1, r);
if (nums[mid] < nums[mid - 1] && nums[mid] > nums[mid + 1]) return find(nums, l, mid - 1);
return -1;
}
}
看完题解后,发现只需要简单二分就行了,10行代码左右
class Solution {
public int findPeakElement(int[] nums) {
int l = 0, r = nums.length - 1;
// 若只有一个元素, 则直接跳出循环
while (l < r) {
int mid = l + r >> 1; // 若只有2个元素, 保证中点取到左侧0
if (nums[mid] > nums[mid + 1]) r = mid;
else l = mid + 1;
}
return l;
}
}
总结:二分,每次都往更高的一端走(人往高处走),一定能找到一个峰值。