二分查找边界模板:第一个 > 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
相关推荐
MediaTea39 分钟前
AI 术语通俗词典:C4.5 算法
人工智能·算法
Navigator_Z1 小时前
LeetCode //C - 1033. Moving Stones Until Consecutive
c语言·算法·leetcode
WBluuue1 小时前
数据结构与算法:莫队(一):普通莫队与带修莫队
c++·算法
风筝在晴天搁浅2 小时前
n个六面的骰子,扔一次之后和为k的概率是多少?
算法
MATLAB代码顾问3 小时前
Python实现蜂群算法优化TSP问题
开发语言·python·算法
代码飞天3 小时前
机器学习算法和函数整理——助力快速查阅
人工智能·算法·机器学习
jiushiapwojdap3 小时前
LU分解法求解线性方程组Matlab实现
数据结构·其他·算法·matlab
笨笨饿3 小时前
69_如何给自己手搓一个串口
linux·c语言·网络·单片机·嵌入式硬件·算法·个人开发
纽扣6674 小时前
【算法进阶之路】链表进阶:删除、合并、回文与排序全解析
数据结构·算法·链表
消失的旧时光-19434 小时前
统一并发模型:线程、Reactor、协程本质是一件事(从线程到协程 · 第6篇·终章)
java·python·算法