题目分析
- 峰值定义:
nums[i] > nums[i-1] && nums[i] > nums[i+1]。 - 特殊条件:
nums[-1] = nums[n] = -∞,保证数组边界的元素也可能是峰值。 - 要求 O(log n) → 使用二分查找。
思路
-
二分查找思想
- 中间元素
mid,判断其与右侧元素的大小关系。 - 如果
nums[mid] < nums[mid + 1],说明峰值在右侧 →left = mid + 1。 - 否则峰值在左侧或就是 mid →
right = mid。
- 中间元素
-
循环终止条件
- 当
left == right时,找到一个峰值,返回索引即可。
- 当
-
为什么可行
- 如果
nums[mid] < nums[mid + 1],说明右侧有上升趋势,必然存在峰值(根据题目保证峰值存在)。 - 否则,左侧或 mid 本身存在峰值。
- 如果
代码
class Solution {
public:
int findPeakElement(vector<int>& nums) {
int left = 0, right = nums.size() - 1;
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] < nums[mid + 1]) {
left = mid + 1;
} else {
right = mid;
}
}
return left;
}
};
复杂度分析
- 时间复杂度:O(log n)
- 空间复杂度:O(1)
学习路线
更多算法学习内容以及算法竞赛题单请访问 「 算法竞赛特训 」
二分法(Binary Search)详细总结
基本概念
二分法是一种在 有序数组或单调序列 中快速定位目标值或满足条件的元素的算法。
核心思想:每次通过中间元素将搜索空间缩小一半。
- 时间复杂度:O(log n)
- 空间复杂度:O(1)
二分法的核心思想
-
定义搜索区间
[left, right]或[left, right)- 区间定义不同,循环条件和返回值会不同。
-
取中点
int mid = left + (right - left) / 2; // 防止溢出 -
根据条件缩小区间
- 如果
mid满足条件 → 移动右边界或左边界 - 如果
mid不满足条件 → 移动另一边界
- 如果
-
循环终止
- 通常是
left == right - 返回
left、right或某个索引
- 通常是
基本模板
模板 1:查找等于目标值
int left = 0, right = n - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target)
return mid; // 找到
else if (nums[mid] < target)
left = mid + 1;
else
right = mid - 1;
}
return -1; // 不存在
- 适合有序数组查找。
[left, right]左闭右闭。
模板 2:寻找最小/最大满足条件的值(单调性)
int left = 0, right = n; // 注意 right = n
while (left < right) { // 左闭右开
int mid = left + (right - left) / 2;
if (check(mid)) // mid 满足条件
right = mid; // 收缩右边界
else
left = mid + 1; // 收缩左边界
}
return left; // 最小满足条件的值
check(mid)必须是 单调函数(true/false 单调变化)。- 适合 二分答案 问题。
模板 3:浮点数二分(精度问题)
double left = 0, right = 1000;
double eps = 1e-6;
while (right - left > eps) {
double mid = (left + right) / 2.0;
if (check(mid))
right = mid;
else
left = mid;
}
return left; // 或 right
- 用于二分 答案 或 精度问题
eps控制精度
| 模板类型 | 搜索区间 | 循环条件 | mid 更新 | 判断条件 | 返回值 | 适用场景 | 核心技巧 | 常见陷阱 |
|---|---|---|---|---|---|---|---|---|
| 查找目标值 | [left, right] 左闭右闭 | while (left <= right) | mid = left + (right-left)/2 | nums[mid] == target / < / > | 找到返回索引,否则 -1 | 有序数组查找 | 左闭右闭保证最后一次循环 left==right 可访问 | 忘记 <=,导致单元素未判断;mid溢出 |
| 寻找最小满足条件 | [left, right) 左闭右开 | while (left < right) | mid = left + (right-left)/2 | check(mid) 是否满足条件 | left 为最小满足条件 | 单调函数 / 二分答案 | check(mid) 单调;收缩右边界 right = mid | 区间收缩错成 right = mid -1,导致漏掉候选值 |
| 寻找最大满足条件 | [left, right) 左闭右开 | while (left < right) | mid = left + (right-left+1)/2 | check(mid) 是否满足条件 | left 为最大满足条件 | 单调函数 / 二分答案 | mid +1 版本确保循环不死锁 | 使用 mid = (left+right)/2 导致死循环 |
| 峰值元素 / 山脉数组 | [0, n-1] | while (left < right) | mid = left + (right-left)/2 | 比较 nums[mid] 与 nums[mid+1] 或 nums[mid-1] | left 或 right | 峰值元素 / 山脉数组 | 利用单调趋势判断峰值方向 | 忘记边界条件导致数组越界;未考虑升序/降序趋势 |
| 浮点数 / 二分答案 | [left, right] | while (right - left > eps) | mid = (left + right)/2 | check(mid) 是否满足精度条件 | left 或 right | 二分答案 / 精度问题 | 设置 eps 精度;避免死循环 | eps 太大导致精度不足;eps 太小导致死循环 |
| 旋转数组 / 特殊单调序列 | [0, n-1] | while (left < right) | mid = left + (right-left)/2 | 根据旋转点或单调性调整 | left 或 right | 旋转数组查找最小值 / 最大值 | 判断 mid 与边界大小关系 | 未考虑旋转后数组单调段;边界判断错 |
| 左右边界 / lower_bound / upper_bound | [0, n] | while (left < right) | mid = left + (right-left)/2 | nums[mid] >= target 或 nums[mid] > target | left 为左/右边界索引 | 查找数组中第一个 >= 或 > target | 明确边界定义(左闭右开) | 忘记加 1 / 不加 1 导致死循环或越界 |
核心总结
-
区间类型
- 左闭右闭
[left, right]→ 查找目标值 - 左闭右开
[left, right)→ 二分答案 / 边界查找
- 左闭右闭
-
mid 计算方式
mid = left + (right-left)/2→ 找最小 / 标准查找mid = left + (right-left+1)/2→ 找最大 / 防死循环
-
循环终止
[left, right]→while(left <= right)[left, right)→while(left < right)
-
边界处理
- 特殊单元素、峰值、旋转数组需考虑
mid+1或mid-1越界
- 特殊单元素、峰值、旋转数组需考虑
-
单调性分析
- 二分法必须依赖序列或条件的单调性:升序/降序或 true/false 单调
-
常见陷阱
- 忘记循环条件或 mid 更新
- 收缩区间方向错导致漏掉候选值
- 浮点数精度过小或过大
- 特殊边界未处理导致越界
408

被折叠的 条评论
为什么被折叠?



