二分查找边界模板:第一个 > 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
相关推荐
Lsk_Smion15 分钟前
Hot100(开刷) 之 长度最小的数组--删除倒数第N个链表--层序遍历
java·数据结构·算法·kotlin
luoganttcc16 分钟前
dim3 grid_size(2, 3, 4); dim3 block_size(4, 8, 4)算例
算法
WBluuue25 分钟前
Codeforces 1088 Div1+2(ABC1C2DEF)
c++·算法
像素猎人34 分钟前
map<数据类型,数据类型> mp和unordered_map<数据类型,数据类型> ump的讲解,蓝桥杯OJ4567最大数目
c++·算法·蓝桥杯·stl·map
Narrastory35 分钟前
Note:强化学习(一)
人工智能·算法·强化学习
沐雪轻挽萤1 小时前
1. C++17新特性-序章
java·c++·算法
郝学胜-神的一滴1 小时前
从链表到二叉树:树形结构的入门与核心性质解析
数据结构·c++·python·算法·链表
csdn_aspnet1 小时前
C语言 (QuickSort using Random Pivoting)使用随机枢轴的快速排序
c语言·算法·排序算法
玖釉-1 小时前
深入解析 meshoptimizer:基于 meshopt_computeSphereBounds 的层级包围球构建与 DAG 优化
c++·算法·图形渲染
语戚1 小时前
力扣 494. 目标和 —— 回溯 & 动态规划双解法全解(Java 实现)
java·算法·leetcode·动态规划·力扣·dp·回溯