思路(参考宫水三叶🙇)
在确保有解的情况之下,我们可以根据 mid 将数组分为两段,根据当前的分割点 mid 与左右元素的大小关系来指导 left 或者 right 的移动,然后判断有没有峰值。
首先得到mid,然后比较 nums[mid] 和 nums[mid+1] ,如果nums[mid] > nums[mid+1]
,说明mid的值大于其右边的值,所以nums[mid]可能为峰值,而 nums[mid+1] 一定不是峰值,于是就让right=mid,从左半部分继续找峰值。
分析
保证上述思路及做法正确有个前提:
-
对于任意数组,一定有峰值(一定有解)
上面特意加粗了 确保有解 ,我们有「数据长度至少为 1」、「越过数组两边看做负无穷」和「相邻元素不相等」的起始条件。- 数组长度为1,且两边边界为负无穷,所以 nums[0] 就是峰值。
- 如果长度大于一:
- 如果 nums[0] > nums[1],那么最左边元素 nums[0] 就是峰值(结合左边界为负无穷)
- 如果 nums[0] < nums[1],由于已经存在明确的 nums[0] 和 nums[1] 大小关系,我们将 nums[0] 看做边界, nums[1] 看做新的最左侧元素,继续往右进行分析:- 如果在到达数组最右侧前,出现 nums[i]>nums[i+1],说明存在峰值位置 i(当我们考虑到 nums[i],必然满足 nums[i] 大于前一元素的前提条件,当然前一元素可能是原始左边界)
- 到达数组最右侧,还没出现 nums[i]>nums[i+1],说明数组严格递增。此时结合右边界可以看做负无穷,可判定 nums[n−1] 为峰值。
-
二分不会错过峰值
如果当前位置大于其左边界或者右边界,那么在当前位置的右边或左边必然存在峰值
我们始终选择大于边界一端进行二分,可以确保选择的区间一定存在峰值,并随着二分过程不断逼近峰值位置。
实现代码(java)
class Solution {
public int findPeakElement(int[] nums) {
int l = 0;
int r = nums.length - 1;
while (l < r) {
int mid = l + (r - l) / 2;
if (nums[mid] > nums[mid+1]) r = mid;
else l = mid + 1;
}
return r;
}
}