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

概念先行(统一记法)

  • 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)
相关推荐
无极低码16 分钟前
ecGlypher新手安装分步指南(标准化流程)
人工智能·算法·自然语言处理·大模型·rag
软件算法开发40 分钟前
基于海象优化算法的LSTM网络模型(WOA-LSTM)的一维时间序列预测matlab仿真
算法·matlab·lstm·一维时间序列预测·woa-lstm·海象优化
superior tigre1 小时前
22 括号生成
算法·深度优先
努力也学不会java2 小时前
【缓存算法】一篇文章带你彻底搞懂面试高频题LRU/LFU
java·数据结构·人工智能·算法·缓存·面试
旖-旎3 小时前
二分查找(x的平方根)(4)
c++·算法·二分查找·力扣·双指针
ECT-OS-JiuHuaShan3 小时前
朱梁万有递归元定理,重构《易经》
算法·重构
智者知已应修善业4 小时前
【51单片机独立按键控制数码管移动反向,2片74CH573/74CH273段和位,按键按下保持原状态】2023-3-25
经验分享·笔记·单片机·嵌入式硬件·算法·51单片机
khddvbe4 小时前
C++并发编程中的死锁避免
开发语言·c++·算法
C羊驼4 小时前
C语言:两天打鱼,三天晒网
c语言·经验分享·笔记·算法·青少年编程
菜菜小狗的学习笔记4 小时前
剑指Offer算法题(四)链表
数据结构·算法·链表