目录

题目描述

852. 山脉数组的峰顶索引

题意分析

给一个「山脉数组」,即存在唯一一个位置 i0 < i < n - 1),使得数组在 [0, i] 上严格递增、在 [i, n - 1] 上严格递减,要求返回这个 i。返回的是下标而不是峰顶的值,这是本题第一个容易看错的地方。

题目条件给得非常慷慨,每一条都在指路。严格递增递减意味着数组里不存在相邻相等的元素,因此比较任意一对相邻元素时,结果非大即小,不会出现模棱两可的平台。唯一峰顶意味着不必考虑多个候选。峰顶不在两端意味着 arr[0] < arr[1]arr[n-2] > arr[n-1] 天然成立,端点永远不是答案。

最强的信号在进阶要求里:数组长度可以到 $10^5$,而题目明确要求在 $O(\log n)$ 时间内完成。从头扫一遍找最大值当然能过测试数据,但它直接违背了题目想考的东西——面试场景下这等于没做。

数组整体当然是无序的,不能直接查找。但换个角度看:把每个位置 i 映射成一个布尔值「arr[i] < arr[i+1] 是否成立」,得到的这串布尔值一定长成 true, true, ..., true, false, ..., false 的形状。峰顶恰好就是第一个 false 出现的位置。一个从 true 单调翻转到 false 的序列,天然允许每次砍掉一半的搜索范围。

边界方面,题目保证 n >= 3,所以至少有一个合法峰顶;而只要把搜索区间的右端限制在 n - 1,访问 arr[mid + 1] 就永远不会越界。

解法:二分查找上升坡结束位置

核心思路

山脉数组先严格上升、再严格下降。比较 arr[mid]arr[mid + 1]

  • arr[mid] < arr[mid + 1]mid 位于上升坡,峰顶一定在右侧;
  • 否则 mid 位于峰顶或下降坡,峰顶一定在 mid 或左侧。

二分始终维护“峰顶位于闭区间 [left, right]”这一不变量。上升时 mid 不可能是答案,令 left = mid + 1;下降时 mid 仍可能是答案,令 right = mid

循环条件 left < right 保证向下取整的 mid 小于 right,所以访问 arr[mid + 1] 安全。区间收敛为单点时,该下标就是唯一峰顶。

解题步骤

  • 初始化搜索区间为 [0, n - 1]
  • left < right 时,计算向下取整的中点。
  • 若中点右侧仍在上升,排除 mid 及其左侧。
  • 否则保留 mid,将右边界收缩到它。
  • 返回最终的 left

例如 [3,4,5,1]:第一次 mid = 14 < 5,区间变为 [2,3];第二次 mid = 25 > 1,区间变为 [2,2],返回峰顶下标 2。

代码实现

class Solution {
    public int peakIndexInMountainArray(int[] arr) {
        int left = 0;
        int right = arr.length - 1;

        while (left < right) {
            int mid = left + (right - left) / 2;
            if (arr[mid] < arr[mid + 1]) {
                left = mid + 1;
            } else {
                right = mid;
            }
        }
        return left;
    }
}
func peakIndexInMountainArray(arr []int) int {
    left, right := 0, len(arr)-1

    for left < right {
        mid := left + (right-left)/2
        if arr[mid] < arr[mid+1] {
            left = mid + 1
        } else {
            right = mid
        }
    }
    return left
}

复杂度分析

  • 时间复杂度:$O(\log n)$。每轮至少排除当前区间的一半。
  • 空间复杂度:$O(1)$。

关键点总结

  • 二分不要求原数组整体有序,只要求判断结果能确定答案所在方向。
  • left = mid + 1right = mid 的差异取决于 mid 能否被排除。
  • left < right、向下取整中点和 right = mid 必须配套使用。
  • 返回的是峰顶下标,不是峰顶元素值。

易错点总结

  • 上升分支写成 left = mid,在两元素区间会死循环。
  • 下降分支写成 right = mid - 1,可能直接丢掉峰顶。
  • 使用 left <= right 却保留 right = mid,单点区间无法收缩。
  • 返回 arr[left] 会得到峰顶值,而题目要求下标。
  • arr[mid - 1] 比较需要额外处理 mid = 0,更容易越界。

相似题目

题目 难度 考察点
162. 寻找峰值 中等 不保证先升后降,可能有多个峰,靠「两端视作负无穷」保证二分必然收敛
1095. 山脉数组中查找目标值 困难 先用本题找峰,再在升降两段各做一次二分,且访问次数受接口限制
941. 有效的山脉数组 简单 反过来判定形状是否合法,必须线性走完并检查峰不在两端
845. 数组中的最长山脉 中等 允许多个山脉且有平台,改用左右两侧的上升长度递推
33. 搜索旋转排序数组 中等 判据换成「哪半边有序」,需要与目标值联合判断走哪边
153. 寻找旋转排序数组中的最小值 中等 同样是找拐点,但拿 arr[mid] 与右端点比较而非与邻居比较
278. 第一个错误的版本 简单 最纯粹的「找第一个 true」,判据由接口给出,可直接套同一份模板
35. 搜索插入位置 简单 找下界,右端要取 n 才能容纳「插到末尾」这个答案
LCR 069. 山脉数组的峰顶索引 简单 与本题同题,可直接套用