深入理解 lower_bound() 和 upper_bound():C++二分查找利器

深入理解 lower_bound() 和 upper_bound():C++二分查找利器

引言

在C++的算法世界中,lower_bound()upper_bound()是两个极其有用的二分查找函数。它们是处理有序序列时的利器,能够在对数时间内完成查找操作。无论你是刷算法题还是进行实际开发,掌握这两个函数都能显著提升代码效率和优雅度。

函数概述

lower_bound()

// 在有序范围[first, last)中查找第一个不小于value的元素
template<class ForwardIt, class T>
ForwardIt lower_bound(ForwardIt first, ForwardIt last, const T& value);

upper_bound()

// 在有序范围[first, last)中查找第一个大于value的元素  
template<class ForwardIt, class T>
ForwardIt upper_bound(ForwardIt first, ForwardIt last, const T& value);

核心区别

函数查找条件返回位置
lower_bound第一个 不小于 value 的元素value的插入点(不破坏顺序)
upper_bound第一个 大于 value 的元素value的最后一个插入点

基础使用示例

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> nums = {1, 2, 4, 4, 5, 6, 7};
    
    // lower_bound 示例
    auto lb = std::lower_bound(nums.begin(), nums.end(), 4);
    std::cout << "lower_bound for 4 at position: " 
              << (lb - nums.begin()) << std::endl;  // 输出: 2
    
    // upper_bound 示例  
    auto ub = std::upper_bound(nums.begin(), nums.end(), 4);
    std::cout << "upper_bound for 4 at position: " 
              << (ub - nums.begin()) << std::endl;  // 输出: 4
    
    // 元素不存在的情况
    auto lb5 = std::lower_bound(nums.begin(), nums.end(), 3);
    std::cout << "lower_bound for 3 at position: " 
              << (lb5 - nums.begin()) << std::endl;  // 输出: 2
    
    return 0;
}

实际应用场景

1. 查找元素是否存在(替代binary_search)

bool exists(const std::vector<int>& arr, int target) {
    auto it = std::lower_bound(arr.begin(), arr.end(), target);
    return it != arr.end() && *it == target;
}

2. 统计元素出现次数

int countOccurrences(const std::vector<int>& arr, int target) {
    auto left = std::lower_bound(arr.begin(), arr.end(), target);
    auto right = std::upper_bound(arr.begin(), arr.end(), target);
    return std::distance(left, right);  // 出现次数
}

3. 插入元素保持有序

void insertSorted(std::vector<int>& arr, int value) {
    auto pos = std::lower_bound(arr.begin(), arr.end(), value);
    arr.insert(pos, value);
}

4. 寻找范围区间

// 在有序数组中查找[L, R]范围内的所有元素
void findRange(const std::vector<int>& arr, int L, int R) {
    auto start = std::lower_bound(arr.begin(), arr.end(), L);
    auto end = std::upper_bound(arr.begin(), arr.end(), R);
    
    for (auto it = start; it != end; ++it) {
        std::cout << *it << " ";
    }
}

自定义比较函数

对于复杂数据结构,可以自定义比较函数:

struct Person {
    std::string name;
    int age;
    
    bool operator<(const Person& other) const {
        return age < other.age;
    }
};

int main() {
    std::vector<Person> people = {
        {"Alice", 25},
        {"Bob", 30},
        {"Charlie", 35},
        {"David", 40}
    };
    
    // 查找年龄不小于32的人
    Person target{"", 32};
    auto it = std::lower_bound(people.begin(), people.end(), target,
        [](const Person& a, const Person& b) {
            return a.age < b.age;
        });
    
    if (it != people.end()) {
        std::cout << "Found: " << it->name << ", " << it->age << std::endl;
    }
    
    return 0;
}

性能分析

时间复杂度

  • 平均情况:O(log n),其中n是序列长度
  • 相比线性查找的O(n),在处理大数据时优势明显

空间复杂度

  • O(1),仅使用常数额外空间

常见错误与注意事项

  1. 容器必须有序

    // 错误示例
    std::vector<int> unsorted = {3, 1, 4, 1, 5};
    auto it = std::lower_bound(unsorted.begin(), unsorted.end(), 3); // 未定义行为
    
  2. 检查返回的迭代器

    // 正确做法
    auto it = std::lower_bound(arr.begin(), arr.end(), target);
    if (it != arr.end() && *it == target) {
        // 找到元素
    }
    
  3. 处理重复元素

    // 获取第一个等于value的位置
    auto first = std::lower_bound(arr.begin(), arr.end(), value);
    // 获取最后一个等于value的位置
    auto last = std::upper_bound(arr.begin(), arr.end(), value);
    // 注意:last指向的是第一个大于value的位置
    

算法题实战

LeetCode 34. 在排序数组中查找元素的第一个和最后一个位置

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        if (nums.empty()) return {-1, -1};
        
        int left = lower_bound(nums.begin(), nums.end(), target) - nums.begin();
        if (left == nums.size() || nums[left] != target) {
            return {-1, -1};
        }
        
        int right = upper_bound(nums.begin(), nums.end(), target) - nums.begin() - 1;
        
        return {left, right};
    }
};

实现简单的二分查找

int binary_search(const std::vector<int>& arr, int target) {
    int left = 0, right = arr.size();
    
    while (left < right) {
        int mid = left + (right - left) / 2;
        
        if (arr[mid] < target) {
            left = mid + 1;
        } else {
            right = mid;
        }
    }
    
    return (left < arr.size() && arr[left] == target) ? left : -1;
}

进阶技巧

1. 在旋转排序数组中查找

int searchInRotatedArray(const std::vector<int>& nums, int target) {
    int left = 0, right = nums.size();
    
    while (left < right) {
        int mid = left + (right - left) / 2;
        
        if (nums[mid] == target) return mid;
        
        if (nums[left] <= nums[mid]) {
            if (nums[left] <= target && target < nums[mid]) {
                right = mid;
            } else {
                left = mid + 1;
            }
        } else {
            if (nums[mid] < target && target <= nums[right - 1]) {
                left = mid + 1;
            } else {
                right = mid;
            }
        }
    }
    
    return -1;
}

2. 查找峰值元素

int findPeakElement(const std::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;
}

记住,这两个函数的前提是序列必须有序,这是使用它们时的金科玉律。

请添加图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

star _chen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值