LeetCode 162:寻找峰值的二分搜索思想与区间不变式分析

题目简介

"Find Peak Element"(LeetCode 162)要求:给定一个 0 下标的整数数组 nums,找出任意一个峰值元素的下标并返回。leetcode

峰值元素定义为:严格大于左右邻居的元素,并且可以把数组两端外面看成 −∞,所以边界元素也可能是峰。leetcode

朴素思路与复杂度要求

最直观的做法是线性扫描:从左到右找第一个满足 nums[i] > nums[i-1] && nums[i] > nums[i+1] 的位置即可,边界单独处理。takeuforward

但题目要求时间复杂度为 O(log n),因此需要用二分搜索思想在"无序数组"上做出类似二分的缩减。algo

关键数学直觉:坡的一侧必有峰

把数组想象成一条从左到右的折线,题目给出两个关键条件:geeksforgeeks

  1. 两端之外为 −∞:nums[-1] = nums[n] = -∞。
  2. 相邻元素不相等:nums[i] != nums[i+1],所以相邻之间要么上坡,要么下坡,没有平坡。geeksforgeeks

于是有两个重要结论(也是二分的核心依据):

如果在某个 mid 位置,nums[mid] < nums[mid+1]:

  • 当前是"上坡"趋势,继续往右走,总会从某个高度掉到右端的 −∞。
  • 在"从高到低"的过程中必然出现某个局部最高点,也就是右侧 [mid+1, right] 中至少存在一个峰值。

如果 nums[mid] > nums[mid+1]:

  • 当前从 mid 到 mid+1 是"下坡",说明在 mid 的左侧(包括 mid)一定从 −∞ 某处上坡再下坡。
  • 这条"先上后下"的路上也必然存在一个局部峰值,因此左侧 [left, mid] 至少有一个峰。

重要的是:这里从来没有说"最高峰在右边/左边",只是说"右边/左边至少存在一个峰",而题目只要求任意一个峰即可。geeksforgeeks

二分算法设计与不变式

利用上述直觉,可以设计如下二分算法(伪代码):

python 复制代码
left, right = 0, len(nums) - 1
while left < right:
    mid = (left + right) // 2
    if nums[mid] > nums[mid + 1]:
        right = mid       # 峰一定在 [left, mid],保留 mid
    else:
        left = mid + 1    # 峰一定在 [mid+1, right],排除 mid
return left               # 或 return right

这里维护的"不变式"是:当前区间 [left, right] 中至少存在一个峰。algo

  • 当 nums[mid] < nums[mid+1]:根据上面的结论,右侧 [mid+1, right] 必有峰,于是把区间更新为 [mid+1, right],丢弃左半边但不破坏"不变式"。algo
  • 当 nums[mid] > nums[mid+1]:左侧 [left, mid] 必有峰,更新为 [left, mid],丢弃右半边,同样保持"不变式"。

每次循环,区间长度 right - left + 1 都至少减 1,且始终保证区间内有峰值存在。

为什么是 right = mid,而不是 mid - 1

在 nums[mid] > nums[mid+1] 的分支里,mid 可能本身就是峰,例如数组 [1, 3, 2] 中 mid 指向 3 时就已经满足"比左右邻居都大"。geeksforgeeks

如果写成 right = mid - 1,就把这个潜在解排除掉了,因此正确写法是 right = mid,保留 mid 在新的搜索区间中。

由于循环条件是 left < right,且 mid 总满足 left <= mid < right,更新后依然有 left <= right,区间非空,算法不会越界或死循环。

为什么是 left = mid + 1,而不是 mid

在 nums[mid] < nums[mid+1] 分支中,mid 明显不可能是峰:右边比它大,已经违反"比左右都大"的定义。algo

同时,右侧 [mid+1, right] 至少有一个峰,因此可以放心排除 mid 本身,将 left 移到 mid+1,从而缩小区间而不丢失解。

如果写成 left = mid,而循环条件仍是 left < right,可能在只剩两个元素时陷入死循环(一直选到同一个 mid),因此这里必须是 mid+1。

为什么循环结束时的位置必然是峰

循环条件是 while (left < right),因此退出时必有 left == right,区间收缩到一个单点 i。algo

结合"不变式"------"当前区间 [left, right] 内至少有一个峰"------可知当 [left, right] 只剩一个元素时,这个元素必须就是那一个峰,否则"不变式"就被破坏了。

因此不需要在循环内部显式地验证 nums[mid-1] < nums[mid] && nums[mid] > nums[mid+1],只要维持好区间不变式并不断缩小区间,最终收敛到的那个单点天然就是峰值位置,可以直接 return left(或 right)。

小结:这一题真正在练什么

  1. 理解"区间不变式":在二分过程中,是否能给出一个"当前区间一定含有解"的数学保证。cp-algorithms
  2. 区分两种模板:
    • 查存在性:while (left <= right),最终可能 left > right,表示区间被"用尽"。
    • 收缩到单点:while (left < right),最终 left == right,区间收缩到一个确定答案。本题属于后一种。
  3. 学会用"单调性 + 解存在性"在无序数组上做二分:这里单调性来自于 nums[i] != nums[i+1] 和两端 −∞ 造出的"上坡/下坡结构"。
相关推荐
cpp_250121 小时前
P2708 硬币翻转
数据结构·c++·算法·题解·洛谷
程序猿阿伟1 天前
《Python复杂结构静态分析秘籍:递归类型注解的深度实践指南》
java·数据结构·算法
bubiyoushang8881 天前
LFM脉冲串信号的模糊函数
算法
踩坑记录1 天前
leetcode hot100 11.盛最多水的容器 medium 双指针
算法·leetcode·职场和发展
圣保罗的大教堂1 天前
leetcode 865. 具有所有最深节点的最小子树 中等
leetcode
码农水水1 天前
中国邮政Java面试:热点Key的探测和本地缓存方案
java·开发语言·windows·缓存·面试·职场和发展·kafka
MM_MS1 天前
Halcon基础知识点及其算子用法
开发语言·人工智能·python·算法·计算机视觉·视觉检测
大厂技术总监下海1 天前
数据湖加速、实时数仓、统一查询层:Apache Doris 如何成为现代数据架构的“高性能中枢”?
大数据·数据库·算法·apache
a程序小傲1 天前
小红书Java面试被问:TCC事务的悬挂、空回滚问题解决方案
java·开发语言·人工智能·后端·python·面试·职场和发展
hetao17338371 天前
2026-01-06 hetao1733837 的刷题笔记
c++·笔记·算法