二分查找模板:基础二分与进阶二分
本人在学习到 @灵茶山艾府 的二分查找专题时,收获颇多,故借助大模型记录一些学习心得。
根据目标不同,二分查找可以分为 基础二分(情况1:查找任意一个目标值)和 进阶二分(查找第一个目标值/最后一个目标值)。二分查找的目标是在一个区间查找目标值,故可将区间分为 均闭区间 [left, right]
、均开区间 (left, right)
、左闭右开 [left, right)
、左开右闭 (left, right]
四种,常用的是 均闭区间。几种类型的区间仅在左右边界的收缩以及循环条件的编码方面有所差异。下面以均闭区间为例总结模板。
均闭区间在进阶二分问题的最终形态为
left > right
,此时left指向最后一个目标值。
1. 基础二分(查找任意一个目标值)
目标:找到数组中是否存在 target
,返回任意一个索引。
模板
int binarySearch(vector<int>& nums, int target) {int left = 0, right = nums.size() - 1;while (left <= right) { // 区间 [left, right]int mid = (left + right) / 2;if (nums[mid] == target) return mid; // 找到直接返回else if (nums[mid] < target) left = mid + 1; // 缩小到右区间else right = mid - 1; // 缩小到左区间}return -1; // 未找到
}
- 条件分支包含
=
,找到直接返回。
2. 进阶二分(情况1):查找第一个目标值(左边界)
目标:找到 target
在数组中第一次出现的位置。
模板
@灵茶山艾府 的Leetcode T34题解
// lower_bound 返回最小的满足 nums[i] >= target 的下标 i
// 如果数组为空,或者所有数都 < target,则返回 nums.size()
// 要求 nums 是非递减的,即 nums[i] <= nums[i + 1]
int lower_bound(vector<int>& nums, int target) {int left = 0, right = (int) nums.size() - 1; // 闭区间 [left, right]while (left <= right) { // 区间不为空// 循环不变量:// nums[left-1] < target// nums[right+1] >= targetint mid = left + (right - left) / 2; // 这种写法的目的是防止 (left + right) / 2 的分子溢出if (nums[mid] >= target) {right = mid - 1; // 范围缩小到 [left, mid-1]} else {left = mid + 1; // 范围缩小到 [mid+1, right]}}// 循环结束后 left = right+1// 此时 nums[left-1] < target 而 nums[left] = nums[right+1] >= target// 所以 left 就是第一个 >= target 的元素下标return left;
}
- 将
=
并入>=
分支,使搜索继续收缩到最左边。
3. 进阶二分(情况2):查找最后一个目标值(右边界)
目标:找到 target
在数组中最后一次出现的位置。
模板
// upper_bound 返回最大的满足 nums[i] <= target 的下标 i
// 如果数组为空,或者所有数都 > target,则返回 -1
// 要求 nums 是非递减的,即 nums[i] <= nums[i + 1]
int upper_bound(vector<int>& nums, int target) {int left = 0, right = (int) nums.size() - 1; // 闭区间 [left, right]while (left <= right) { // 区间不为空// 循环不变量:// nums[left-1] <= target// nums[right+1] > targetint mid = left + (right - left) / 2; // 这种写法的目的是防止 (left + right) / 2 的分子溢出if (nums[mid] <= target) {left = mid + 1; // 范围缩小到 [mid+1, right]} else {right = mid - 1; // 范围缩小到 [left, mid-1]}}// 循环结束后 right = left-1// 此时 nums[right] <= target 且 nums[left] = nums[right+1] > target// 所以 right 就是最后一个 <= target 的元素下标return right;
}
- 将
=
并入<=
分支,使搜索继续收缩到最右边。
4. 总结
- 基础二分(任意值):
=
时立即返回。 - 进阶二分(左边界):
=
与>
合并,收缩右边界。 - 进阶二分(右边界):
=
与<
合并,收缩左边界。 - 区间选择:不同区间定义(闭/开)影响循环条件和更新方式,模板需统一风格。建议使用均开/均闭区间的模板。