1. 二分查找的边界模板(左闭右闭/左闭右开)如何统一记忆?死循环与越界的根因分析?
请给出二分查找中左闭右闭 [l,r] 与左闭右开 [l,r) 两种模板的标准写法,说明如何统一记忆,并分析死循环与数组越界的根因?
- 两种区间的维护方式与循环不变量差异
- 中值取法 mid 与区间收缩的对应关系
- 死循环(l 不再增大)与越界(r 出界)的根因
左闭右闭 [l,r]:l=0, r=n-1,while(l<=r),mid=(l+r)/2。若 a[mid]<target 则 l=mid+1,否则 r=mid-1。循环结束时 l>r,l 为 insertion point。左闭右开 [l,r):l=0, r=n,while(l<r),mid=(l+r)/2。若 a[mid]<target 则 l=mid+1,否则 r=mid。循环结束时 l==r,即第一个≥target 的位置(lower_bound)。统一记忆法:① 始终让"可行域"保持区间不变式,[l,r) 中 r 是"开"的,所以 r=mid 不排除 mid 本身;② 计算 mid 时用 mid=l+(r-l)/2 防溢出;③ 左闭右开中当 l+1==r 时 mid==l,若进入"l=mid"分支会死循环,故必须写 l=mid+1(或 r=mid)。死循环根因:更新时把 l 或 r 赋成 mid 而非 mid±1,导致区间长度不缩。越界根因:左闭右开把 r 初始化为 n 后,若访问 a[r] 会越界;或左闭右闭 r 初始化为 n 而非 n-1。
两种模板本质是同一逻辑,只是"开区间端点"的包容语义不同。左闭右闭强调 while(l<=r) 且两端都收缩;左闭右开强调 while(l<r) 且 r=mid 不收缩该点。统一记忆的关键是"不变式"——每次循环后搜索区间仍满足[l,r)性质,且 mid 的归属决定 l 或 r 的赋值。防溢出 mid=l+(r-l)/2 是高频考点。
// 左闭右开 [l,r):返回第一个 >= target 的下标(lower_bound)
int lowerBound(int[] a, int target) {
int l = 0, r = a.length; // r 为开区间
while (l < r) {
int mid = l + (r - l) / 2;
if (a[mid] < target) l = mid + 1; // mid 排除,l 前进
else r = mid; // mid 保留,r 收缩
}
return l; // l == r
}
// 左闭右闭 [l,r]:经典查找
int search(int[] a, int target) {
int l = 0, r = a.length - 1;
while (l <= r) {
int mid = l + (r - l) / 2;
if (a[mid] == target) return mid;
else if (a[mid] < target) l = mid + 1;
else r = mid - 1;
}
return -1;
}