binary search lower/upper bound

343 阅读6分钟

二分查找是一个非常重要的算法技巧,常用于 有序数组 或范围内的搜索。对于 lower boundupper bound,它们分别对应找到目标值的第一个位置和最后一个位置(或者一个范围之外的位置)。


基本定义

  1. Lower Bound:

    • 找到数组中 第一个大于等于目标值 的元素位置。
    • 如果目标值存在,就是目标值的第一个位置;如果不存在,就是插入点的位置。
  2. Upper Bound:

    • 找到数组中 第一个大于目标值 的元素位置。
    • 如果目标值存在,它指向目标值的最后一个位置的下一位;如果不存在,也是插入点的位置。

示例问题

给定有序数组 nums = [1, 3, 3, 5, 7] 和目标值 target = 3

  • Lower Bound: 应返回索引 1(第一个 3 的位置)。
  • Upper Bound: 应返回索引 3(第一个大于 3 的位置,即 5)。

实现代码

Lower Bound (第一个大于等于目标值的位置)

int lowerBound(vector<int>& nums, int target) {
    int left = 0, right = nums.size(); // [left, right)
    while (left < right) {
        int mid = left + (right - left) / 2;
        if (nums[mid] < target) {
            left = mid + 1; // 排除小于 target 的部分
        } else {
            right = mid; // 包含 >= target 的部分
        }
    }
    return left; // 此时 left == right
}

Upper Bound (第一个大于目标值的位置)

int upperBound(vector<int>& nums, int target) {
    int left = 0, right = nums.size(); // [left, right)
    while (left < right) {
        int mid = left + (right - left) / 2;
        if (nums[mid] <= target) {
            left = mid + 1; // 排除 <= target 的部分
        } else {
            right = mid; // 包含 > target 的部分
        }
    }
    return left; // 此时 left == right
}

标准库 std::lower_boundstd::upper_bound

C++ STL 中提供了这两个功能,定义在 <algorithm> 中。

使用方法

#include <algorithm>
#include <vector>
#include <iostream>
using namespace std;

int main() {
    vector<int> nums = {1, 3, 3, 5, 7};
    int target = 3;

    // Lower Bound
    int lb = lower_bound(nums.begin(), nums.end(), target) - nums.begin();
    cout << "Lower Bound: " << lb << endl; // 输出 1

    // Upper Bound
    int ub = upper_bound(nums.begin(), nums.end(), target) - nums.begin();
    cout << "Upper Bound: " << ub << endl; // 输出 3

    return 0;
}

注意

  • std::lower_bound 返回的是第一个大于等于 target 的迭代器。如果所有元素都小于 targetupper_bound 会返回 nums.end()
  • std::upper_bound 返回的是第一个大于 target 的迭代器。所有元素都小于 target,返回nums.end()

比较 Lower Bound 和 Upper Bound

类型目标返回值
lower_bound>= target第一个大于等于目标值的位置。
upper_bound> target第一个严格大于目标值的位置。

应用场景

  1. 查找范围

    • 找出所有等于 target 的元素。
    • 范围是 [lower_bound, upper_bound)
    auto start = lower_bound(nums.begin(), nums.end(), target);
    auto end = upper_bound(nums.begin(), nums.end(), target);
    cout << "Count: " << end - start << endl; // 范围内元素个数
    
  2. 插入位置

    • lower_bound 可用于找插入位置,使数组仍然有序。
  3. 搜索问题

    • 在某些区间问题中使用 upper_boundlower_bound 来缩小范围。

总结

  • lower_boundupper_bound 的核心在于二分查找。
  • lower_bound 寻找第一个大于等于目标值的位置,upper_bound 寻找第一个大于目标值的位置。
  • 二者结合,可以快速解决范围查询或有序插入问题。

以下是一些与 二分查找lower boundupper bound 相关的经典 LeetCode 题目。这些题目中,有的直接用到了 lower_boundupper_bound 的思想,有的则是二分查找的变种应用。

经典二分查找类问题

1. 704. Binary Search

  • 描述: 在一个有序数组中找到目标值,返回其索引。如果不存在,返回 -1。
  • 关键词: 基本的二分查找。
  • LeetCode 704

2. 35. Search Insert Position

  • 描述: 找到目标值在数组中的插入位置。如果目标值存在,返回其索引;否则返回插入点。
  • 关键词: lower_bound 应用。
  • LeetCode 35

3. 34. Find First and Last Position of Element in Sorted Array

  • 描述: 找到目标值在数组中的起始和结束位置。如果不存在,返回 [-1, -1]。
  • 关键词: lower_boundupper_bound 的结合应用。
  • LeetCode 34
meta vo 变种题

sorted array 中,是否有超过 n/3 的数

for(int i = 0; i < nums.size(); i+= n/3){
  //用upper_bound, lower_bound 检查是否超过n/3 超过 返回true 否则 返回false
}

4. 2363. Merge Similar Items(Bloomberg VO)

  • 描述: 合并两个包含键值对的数组,将权重相加,并按键值升序返回结果。
  • 关键词: 哈希表存储合并权重,排序生成结果。
  • LeetCode 2363

5. 69. Sqrt(x)

  • 描述: 实现整数平方根函数,返回小于等于平方根的最大整数值。
  • 关键词: 二分查找在数学问题中的应用。
  • LeetCode 69

6. 374. Guess Number Higher or Lower

  • 描述: 猜数字游戏,目标是通过调用 API 找到目标值。
  • 关键词: 基础二分查找。
  • LeetCode 374

高级二分查找及其变种

7. 162. Find Peak Element

  • 描述: 找到一个峰值元素,峰值满足 nums[i] > nums[i-1]nums[i] > nums[i+1]
  • 关键词: 二分查找的条件变化。
  • LeetCode 162

8. 153. Find Minimum in Rotated Sorted Array

  • 描述: 在旋转排序数组中找到最小值。
  • 关键词: 二分查找在旋转数组中的应用。
  • LeetCode 153

9. 154. Find Minimum in Rotated Sorted Array II

  • 描述: 在旋转排序数组中找到最小值,数组中允许存在重复值。
  • 关键词: 二分查找处理边界条件。
  • LeetCode 154

10. 33. Search in Rotated Sorted Array

  • 描述: 在旋转排序数组中找到目标值,返回其索引。如果不存在,返回 -1。
  • 关键词: 二分查找处理旋转。
  • LeetCode 33

11. 658. Find K Closest Elements

  • 描述: 在数组中找到与目标值最接近的 k 个元素,返回排序后的结果。
  • 关键词: 二分查找范围。
  • LeetCode 658

数学与区间问题结合的二分查找

11. 410. Split Array Largest Sum

  • 描述: 将数组分成 m 段,使得段内和的最大值最小化。
  • 关键词: 二分查找 + 贪心。
  • LeetCode 410

12. 287. Find the Duplicate Number

  • 描述: 在一个范围为 [1, n] 的数组中找到重复的数字。
  • 关键词: 二分查找用于抽象范围,而非数组本身。
  • LeetCode 287

13. 378. Kth Smallest Element in a Sorted Matrix

  • 描述: 找到排序矩阵中的第 k 小元素。
  • 关键词: 二分查找范围。
  • LeetCode 378

14. 302. Smallest Rectangle Enclosing Black Pixels

  • 描述: 在二维数组中找到包含所有黑色像素的最小矩形。
  • 关键词: 二分查找在二维范围的应用。
  • LeetCode 302

总结

  • 基础二分查找问题:例如 70435,专注于实现标准的二分查找。
  • lower_boundupper_bound 问题:例如 3435,处理范围和插入点问题。
  • 高级应用:例如旋转数组和峰值问题,考察对条件的灵活处理。
  • 范围缩小问题:例如 410287,将数组的二分思想扩展到值或区间。