题目重点:从线性扫描基线到严格单峰数组二分,解释多峰时为何无法无条件保留 O(log n) 的全局最大保证。
题意与假设
这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。
- 训练二分版数组严格先增后减,峰可在边界;无相等平台。题目只说一个 peak,严格单峰是为保证二分正确的额外约定。
- 多峰版要求全局最大值位置,若并列取最早位置;空数组报错。
解题路线
- 任意数组先做线性基线,逐项维护最大值与位置,O(N) 且不依赖形状。
- 严格单峰时比较 values[mid] 与 values[mid+1]:若上升,峰位于右侧含 mid+1;否则位于左侧含 mid。每次保留峰所在半区。
- low==high 时即峰值下标。归纳不变量是唯一峰一直位于 [low,high];严格单峰使局部斜率能决定唯一峰方向。
- 任意多峰时局部上升只能说明右边存在局部峰,不能排除左边有更高峰;若没有其他结构保证,必须扫描以找全局最大。
手动走一遍
[1,4,7,5,2] 比较中点 7 与 5 后保留左半,最终下标 2。多峰 [9,1,2,3] 在中间右侧上升,却全局最大在左端,照搬单峰二分会错。
Python 参考实现
下面的代码按上文假设实现,适合本地运行并追加自己的测试用例。
代码区可横向滚动;使用键盘时,先聚焦代码区再按方向键查看。
"""Find the unique peak of a strictly increasing-then-decreasing sequence."""
def unique_peak_index(values: list[int]) -> int:
if not values:
raise ValueError("values must be nonempty")
low, high = 0, len(values) - 1
while low < high:
middle = (low + high) // 2
if values[middle] < values[middle + 1]:
low = middle + 1
else:
high = middle
return low
def global_max_index(values: list[int]) -> int:
if not values:
raise ValueError("values must be nonempty")
return max(range(len(values)), key=lambda index: values[index])
复杂度
严格单峰找峰 O(log N) 时间、O(1) 空间;任意多峰找全局最大 O(N) 时间、O(1) 空间。若仅要任一局部峰,可有不同的二分问题,不能与全局最大混同。
易错点与追问
容易失分的边界
- 把一个局部峰与全局最大峰混为一谈。
- 有平台或噪声时仍直接宣称严格单峰二分正确。
- 把 CPU 采样原始噪声数据当成已满足单峰的聚合数组。
继续深挖
- 若有平顶区间,如何定义返回最左、最右或任意峰?
- 若允许少量噪声而总体单峰,如何在正确性与近似之间取舍?
