半开区间和开区间的两个二分模版

概念先行(统一记法)

  • leftright:区间端点。

  • mid = left + (right - left) / 2mid = (left + right) >>> 1(避免溢出)。

  • n:数组长度,合法下标是 0 .. n-1

  • two styles:

    • 半开区间 [left, right):右端点不包含(常用在查找插入位置 / lower_bound / upper_bound)。
    • 闭区间 [left, right]:左右端点都包含(常用在查找是否存在某值并返回下标)。

一、半开区间([left, right))模板 ------ 推荐用于 lower_bound / 插入点

不变量(重要)

目标插入位置始终位于区间 [left, right) 中(右端点不包含)。

结构(Java)

ini 复制代码
int left = 0, right = n; // right = n -> 表示 [0, n)
while (left < right) {
    int mid = (left + right) >>> 1; // or left + (right-left)/2
    if (A[mid] < target) {
        left = mid + 1;   // 插入位置在右半区: [mid+1, right)
    } else {
        right = mid;      // 插入位置在左半区(包含 mid): [left, mid)
    }
}
// loop 结束时 left == right,即插入位置
int pos = left;

常见变体

  • lower_bound(第一个 >= target) :上面模板正是 lower_bound。

  • upper_bound(第一个 > target)

    ini 复制代码
    // 半开区间,right = n
    while (left < right) {
        int mid = (left + right) >>> 1;
        if (A[mid] <= target) left = mid + 1;
        else right = mid;
    }
    pos = left; // 第一个 > target 的下标

优点

  • 简洁、不易错的模板(左闭右开避免 mid == right 情况)。
  • 自然返回"插入位置",适合维护 tails 这类需要插入点的场景。

二、闭区间([left, right])模板 ------ 适合查找是否存在且返回下标

不变量(重要)

目标位置始终在 [left, right] 中,循环条件通常是 left <= right

结构(Java)------ 查找"是否存在并返回任意下标"

ini 复制代码
int left = 0, right = n - 1;
while (left <= right) {
    int mid = (left + right) >>> 1;
    if (A[mid] == target) return mid;
    else if (A[mid] < target) left = mid + 1;
    else right = mid - 1;
}
// not found -> left is insertion point, right = left - 1

变体:用闭区间实现 lower_bound(第一个 >= target)

ini 复制代码
int left = 0, right = n - 1;
int pos = n; // 默认插入到末尾
while (left <= right) {
    int mid = (left + right) >>> 1;
    if (A[mid] >= target) {
        pos = mid;      // candidate
        right = mid - 1; // 缩到左半区
    } else {
        left = mid + 1;
    }
}
// pos 是第一个 >= target(可能为 n 表示 append)

注意点(闭区间常见错误)

  • 当要把 mid 作为"候选"(A[mid] >= target)时,必须做 right = mid - 1,否则区间不会收缩(会死循环)。
  • 初始化 right 要是 n - 1(不是 n)。否则访问 A[mid] 会越界。

三、对比与选用建议(实用)

  • 想要插入位置 / lower_bound / upper_bound -> 用半开区间模板(更简洁、易用)。
  • 单纯判断是否存在并返回下标 -> 用闭区间模板(直观)。
  • 半开区间:left=0,right=n, while(left<right), right=mid
  • 闭区间:left=0,right=n-1, while(left<=right), right=mid-1(当 mid 作为 candidate 时)。

四、常见错误 & 排查清单

  1. 越界right 设为 n 却用 while(left<=right),会算出 mid==n → 访问 A[n] 越界。
  2. 死循环 :闭区间写 while(left<=right) 却在候选分支写 right = mid(而非 mid-1)会导致区间不收缩。
  3. off-by-one :不清楚 mid 属于哪半区,更新时要保证区间长度严格减小。
  4. mid 溢出 :用 (left+right)/2 在极端大整数下溢出,改用 left + (right - left)/2>>>1
  5. 空数组 :在半开区间 right = 0,循环不进,pos=0 正确;闭区间 right = -1,要注意 while(left<=right) 不进并处理返回值。

五、快速模板汇总(Java)

半开区间 lower_bound(第一个 >= target):

ini 复制代码
int left = 0, right = n; // [left, right)
while (left < right) {
    int mid = (left + right) >>> 1;
    if (A[mid] < target) left = mid + 1;
    else right = mid;
}
int pos = left;

半开区间 upper_bound(第一个 > target):

ini 复制代码
int left = 0, right = n;
while (left < right) {
    int mid = (left + right) >>> 1;
    if (A[mid] <= target) left = mid + 1;
    else right = mid;
}
int pos = left;

闭区间查找 exact match(是否存在):

ini 复制代码
int left = 0, right = n - 1;
while (left <= right) {
    int mid = (left + right) >>> 1;
    if (A[mid] == target) return mid;
    else if (A[mid] < target) left = mid + 1;
    else right = mid - 1;
}
return -1;

闭区间 lower_bound(第一个 >= target):

ini 复制代码
int left = 0, right = n - 1;
int pos = n;
while (left <= right) {
    int mid = (left + right) >>> 1;
    if (A[mid] >= target) { pos = mid; right = mid - 1; }
    else left = mid + 1;
}
// pos is first >= target, may be n (not found -> insert at end)

六、举例演练(半开区间找 lower_bound)

数组 A=[2,3,7,101], n=4, target=18:

  • left=0,right=4 -> mid=2(A[2]=7)<18 -> left=3
  • left=3,right=4 -> mid=3(A[3]=101)>=18 -> right=3
  • left==right==3 -> pos=3(正确,替换 101)
相关推荐
moonlifesudo1 小时前
300:最长递增子序列
算法
CoovallyAIHub6 小时前
港大&字节重磅发布DanceGRPO:突破视觉生成RLHF瓶颈,多项任务性能提升超180%!
深度学习·算法·计算机视觉
CoovallyAIHub7 小时前
英伟达ViPE重磅发布!解决3D感知难题,SLAM+深度学习完美融合(附带数据集下载地址)
深度学习·算法·计算机视觉
聚客AI1 天前
🙋‍♀️Transformer训练与推理全流程:从输入处理到输出生成
人工智能·算法·llm
大怪v1 天前
前端:人工智能?我也会啊!来个花活,😎😎😎“自动驾驶”整起!
前端·javascript·算法
惯导马工1 天前
【论文导读】ORB-SLAM3:An Accurate Open-Source Library for Visual, Visual-Inertial and
深度学习·算法
骑自行车的码农1 天前
【React用到的一些算法】游标和栈
算法·react.js
博笙困了1 天前
AcWing学习——双指针算法
c++·算法
moonlifesudo1 天前
322:零钱兑换(三种方法)
算法