二分查找边界模板:第一个 > target / 第一个 < target(找不到就返回边界)

二分查找边界模板:第一个 > target / 第一个 < target(找不到就返回边界)

写二分最容易错的不是"能不能找到",而是 边界怎么收、mid 怎么取、以及找不到时返回什么。这篇把两个常用的"边界型二分"整理成可直接套用的模板。

本文假设数组 a 在区间 [l, r]单调非降(升序可重复)


1)第一个 > target 的下标(upper_bound)

语义

a[l..r] 中找 最左 的下标 idx 满足:

  • a[idx] > target

如果不存在(也就是全都 <= target),返回边界 r(夹到右边界)。

说明:经典 upper_bound 不存在时返回 r+1,但你要求"查不到就返回边界",这里做了"夹边界"处理。

模板(Java)

java 复制代码
// 第一个 > target;如果不存在,返回 r(右边界)
static int firstGreaterOrRight(int[] a, int l, int r, int target) {
    int lo = l, hi = r + 1; // 搜索落点允许到 r+1(虚拟位置,不访问数组)
    while (lo < hi) {
        int mid = lo + ((hi - lo) >>> 1);
        if (a[mid] > target) hi = mid;
        else lo = mid + 1;
    }
    // lo 是"第一个 > target"的位置,可能是 r+1
    return (lo == r + 1) ? r : lo;
}

关键点

  • hi = r+1 是"虚拟位置",不会访问 a[r+1] ,因为 mid < hi,所以 mid 最大只会到 r
  • 最后 lo 可能为 r+1,表示不存在,于是按需求返回 r

2)第一个 < target 的下标(最左的"小于")

语义

a[l..r] 中找 最左 的下标 idx 满足:

  • a[idx] < target

如果不存在(也就是全都 >= target),返回边界 l(夹到左边界)。

注意:这不是"最后一个 < target",而是"第一个 < target"(最左的小于)。

模板(Java)

思路:

"第一个 < target" 等价于在单调数组中找 第一个 >= target 的位置 lb(lower_bound),那么答案就是:

  • 如果 lb == l:说明没有 < target,按需求返回 l
  • 否则:最左 < target 就是 l(因为 lb 之前都 < target,最左必然是 l

但这推导对"最左"很尴尬:只要存在 < target,最左一定是 l

所以更有用、也更符合你想表达"边界二分"的,通常写的是:

第一个 >= target (lower_bound)

或 ✅ 最后一个 < target

你这里写"第一个小于target"很可能是口误,实际想要的是"最后一个小于 target"。我直接给你三者都放上,博客更完整,也不坑你。


2A)第一个 >= target(lower_bound,强烈推荐你用这个)

语义

找最左 idx 满足 a[idx] >= target

不存在时返回 r(夹到右边界)

java 复制代码
// 第一个 >= target;如果不存在,返回 r
static int firstGeOrRight(int[] a, int l, int r, int target) {
    int lo = l, hi = r + 1;
    while (lo < hi) {
        int mid = lo + ((hi - lo) >>> 1);
        if (a[mid] >= target) hi = mid;
        else lo = mid + 1;
    }
    return (lo == r + 1) ? r : lo;
}

2B)最后一个 < target(更常用,且和"第一个 >"对偶)

语义

找最右 idx 满足 a[idx] < target

不存在时返回 l(夹到左边界)

java 复制代码
// 最后一个 < target;如果不存在,返回 l
static int lastLessOrLeft(int[] a, int l, int r, int target) {
    int lo = l - 1, hi = r;   // lo 允许落到 l-1(虚拟位置,不访问数组)
    while (lo < hi) {
        int mid = lo + ((hi - lo + 1) >>> 1); // 上中位数,避免死循环
        if (a[mid] < target) lo = mid;
        else hi = mid - 1;
    }
    // lo 可能是 l-1 表示不存在
    return (lo == l - 1) ? l : lo;
}

3)为什么"虚拟边界"不会越界?

  • hi = r+1 的模板里,mid 永远满足 mid < hi,因此 mid <= r,不会访问 a[r+1]
  • lo = l-1 的模板里,我们用 上中位数 ,保证 mid >= lo+1 >= l,不会访问 a[l-1]

4)一眼记住的对偶关系

需求 典型模板 可能的"找不到"落点 按本文"夹边界"返回
第一个 > target hi = r+1,mid 下取 r+1 r
第一个 >= target hi = r+1,mid 下取 r+1 r
最后一个 < target lo = l-1,mid 上取 l-1 l

5)小结:推荐你真正常用的两个

如果你在做区间、前缀和、离线查询、重复元素统计,最常用就是这俩:

  • lower_bound :第一个 >= target
  • upper_bound :第一个 > target

有了它们:

  • count(target) = upper_bound(target) - lower_bound(target)
  • "最后一个 <= target" = upper_bound(target) - 1
  • "最后一个 < target" = lower_bound(target) - 1
相关推荐
_OP_CHEN9 小时前
【算法基础篇】(五十六)容斥原理指南:从集合计数到算法实战,解决组合数学的 “重叠难题”!
算法·蓝桥杯·c/c++·组合数学·容斥原理·算法竞赛·acm/icpc
TracyCoder1239 小时前
LeetCode Hot100(27/100)——94. 二叉树的中序遍历
算法·leetcode
九.九9 小时前
CANN HCOMM 底层机制深度解析:集合通信算法实现、RoCE 网络协议栈优化与多级同步原语
网络·网络协议·算法
C++ 老炮儿的技术栈9 小时前
Qt Creator中不写代如何设置 QLabel的颜色
c语言·开发语言·c++·qt·算法
子春一9 小时前
Flutter for OpenHarmony:构建一个 Flutter 数字消消乐游戏,深入解析网格状态管理、合并算法与重力系统
算法·flutter·游戏
草履虫建模16 小时前
力扣算法 1768. 交替合并字符串
java·开发语言·算法·leetcode·职场和发展·idea·基础
naruto_lnq18 小时前
分布式系统安全通信
开发语言·c++·算法
Jasmine_llq18 小时前
《P3157 [CQOI2011] 动态逆序对》
算法·cdq 分治·动态问题静态化+双向偏序统计·树状数组(高效统计元素大小关系·排序算法(预处理偏序和时间戳)·前缀和(合并单个贡献为总逆序对·动态问题静态化
爱吃rabbit的mq19 小时前
第09章:随机森林:集成学习的威力
算法·随机森林·集成学习
(❁´◡`❁)Jimmy(❁´◡`❁)19 小时前
Exgcd 学习笔记
笔记·学习·算法