深入解析二分查找算法:原理、实现与变种
目录
一、核心思想
二、前提条件
三、标准二分查找实现
场景:在有序数组中查找某个值是否存在。
关键点:
四、变种问题与实现
1. 查找第一个等于目标的位置(Lower Bound)
2. 查找最后一个等于目标的位置(Upper Bound)
3. 查找插入位置
五、STL中的二分查找
六、常见问题与陷阱
七、应用场景
八、总结
一、核心思想
二分查找(Binary Search)是一种在有序数组中快速查找目标值的算法。其核心思想是:
-
分治策略:通过不断将搜索区间缩小一半来逼近目标值。
-
时间复杂度:O(log n),效率远高于线性查找的 O(n)。
二、前提条件
-
数组必须有序(升序或降序)。
-
若数组无序,需先排序(时间复杂度 O(n log n))。
三、标准二分查找实现
场景:在有序数组中查找某个值是否存在。
int binarySearch(const vector<int>& nums, int target) {
int left = 0, right = nums.size() - 1; // 闭区间 [left, right]
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
,确保区间非空时继续搜索。 -
中间值计算:使用
mid = left + (right - left) / 2
而非(left + right) / 2
,避免整数溢出。 -
边界调整:
-
left = mid + 1
:排除左半区。 -
right = mid - 1
:排除右半区。
-
四、变种问题与实现
1. 查找第一个等于目标的位置(Lower Bound)
int findFirst(const vector<int>& nums, int target) {
int left = 0, right = nums.size() - 1;
int res = -1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] >= target) {
right = mid - 1; // 向左收缩,寻找左边界
if (nums[mid] == target) res = mid;
} else {
left = mid + 1;
}
}
return res;
}
2. 查找最后一个等于目标的位置(Upper Bound)
int findLast(const vector<int>& nums, int target) {
int left = 0, right = nums.size() - 1;
int res = -1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] <= target) {
left = mid + 1; // 向右收缩,寻找右边界
if (nums[mid] == target) res = mid;
} else {
right = mid - 1;
}
}
return res;
}
3. 查找插入位置
int searchInsert(const vector<int>& nums, int target) {
int left = 0, right = nums.size() - 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 left; // 返回插入位置
}
五、STL中的二分查找
C++标准库提供了以下二分查找工具:
-
std::binary_search
:检查元素是否存在。bool exists = binary_search(nums.begin(), nums.end(), target);
-
std::lower_bound
:返回第一个 >= target 的迭代器。auto it = lower_bound(nums.begin(), nums.end(), target); int index = it - nums.begin();
-
std::upper_bound
:返回第一个 > target 的迭代器。auto it = upper_bound(nums.begin(), nums.end(), target);
六、常见问题与陷阱
-
死循环:
-
错误调整边界会导致循环无法终止,如
left = mid
而非mid + 1
。
-
-
边界条件:
-
未正确处理
left
和right
的更新。
-
-
溢出问题:
-
使用
(left + right) / 2
可能溢出,应改用left + (right - left) / 2
。
-
-
未排序数组:
-
二分查找要求数组有序,否则结果不可预测。
-
七、应用场景
-
有序数组的快速查找。
-
寻找数值解(如平方根、方程根)。
-
旋转排序数组中的最小值(如
[4,5,6,7,0,1,2]
)。 -
山脉数组(先增后减)的峰值查找。
八、总结
-
核心思想:分治策略,每次缩小一半搜索范围。
-
关键点:循环终止条件、边界调整、中间值计算。
-
适用条件:有序数组。
-
变种问题:需根据具体需求调整边界条件和返回值。