https://blog.csdn.net/2601_95366422/article/details/158770218
上节课链接
一.题目

二.思考讲解
第一步:查找二分性
观察题目描述,数组先递增到一个峰值元素 ,然后递减。这种"先升后降"的单峰特性意味着:在峰值左侧,元素满足 arr[i] < arr[i+1](上升趋势);在峰值右侧,元素满足 arr[i] > arr[i+1](下降趋势)。因此,如果我们取任意一个位置 mid,比较它和它右边的元素 arr[mid+1],就可以判断当前位于峰值的哪一侧:
-
如果
arr[mid] < arr[mid+1],说明mid处于上升段 ,峰值一定在mid的右边。 -
如果
arr[mid] > arr[mid+1],说明mid处于下降段 或正好是峰值,峰值在mid的左边或就是mid本身。 这种"一侧上升一侧下降"的规律,使得我们可以通过比较相邻元素来不断缩小搜索范围,因此数组具备二分性,可以采用二分查找求解。
第二步:采用左端点还是右端点
我们要找的是峰值元素 ,即第一个满足 arr[i] > arr[i+1] 的位置 i(因为一旦进入下降段,第一个下降点就是峰顶)。这是一个典型的寻找第一个满足某条件的位置 问题,因此应该采用左端点二分查找 。用现实中的山来比喻:站在山脚往上看,最先看到的山顶就是第一个出现的最高点,后面即使有更高的山也被遮挡,所以我们要找的是最左边的峰值 。采用左端点模板,中点取向下取整,缩进规则为:
-
当
arr[mid] < arr[mid+1]时,说明mid还在上升段,峰值在右边,因此left = mid + 1(舍弃左半区间)。 -
当
arr[mid] > arr[mid+1]时,说明mid在下降段或就是峰值,峰值在左边或就是mid,因此right = mid(保留mid继续搜索)。 循环结束时,left指向的就是第一个下降点,即峰顶索引。
三.代码演示
cpp
class Solution {
public:
int peakIndexInMountainArray(vector<int>& arr)
{
int left = 0;
int right = arr.size() - 1;
while(left < right)
{
int mid = left + (right - left)/2;
if(arr[mid] < arr[mid + 1])
{
left = mid + 1;
}
else
right = mid;
}
return left;
}
};
四.代码讲解
一、循环条件与中点计算
采用 while (left < right) 作为循环条件,而不是 left <= right。这是因为当左右指针相遇时,区间内只剩一个元素,此时无需继续二分 ,可以直接在循环外返回该元素。这种写法可以避免死循环,并符合左端点模板的常规写法。
中点计算采用向下取整 的公式,即 mid = left + (right - left) / 2。当区间长度为偶数时,中点会偏向左边 ,这确保了当 left 和 right 相邻时,中点指向 left,从而保证区间能够正确收缩,不会出现 right 原地不动的情况。向下取整 是左端点查找的精髓,也是避免死循环的关键。
二、区间缩进规则
在循环中,根据 arr[mid] 与 arr[mid + 1] 的比较结果,按照以下规则更新指针:
-
当
arr[mid] < arr[mid + 1]时 :说明mid处于上升段 ,峰值一定在mid的右侧。因为mid本身不可能是峰值(它小于右边),所以可以安全地将左指针移动到mid + 1,即舍弃左半区间。 -
当
arr[mid] > arr[mid + 1]时 :说明mid处于下降段 或正好是峰值。此时峰值可能在mid左侧,也可能就是mid本身。因此将右指针移动到mid,保留mid在搜索区间内 ,继续向左寻找。注意这里不能用right = mid - 1,因为mid有可能是峰值,必须保留。
三、循环结束后的处理
循环结束时,left 与 right 相等,此时指向的位置就是第一个满足 arr[i] > arr[i+1] 的位置 ,即峰顶索引。直接返回 left 即可,无需额外判断,因为题目保证数组至少有三个元素且存在峰值。
四、细节注意
-
循环中比较的是
arr[mid]和arr[mid+1],因此需要保证mid+1不越界。由于循环条件left < right且right初始为size-1,在循环过程中mid最大为right-1(因为向下取整,当left和right相邻时,mid = left,此时mid+1 = right合法),所以不会越界。 -
初始
left = 0,right = arr.size() - 1,这覆盖了整个数组。