二分查找细节理解

要理解二分查找中 while(l < r)while(l <= r) 的区别,核心在于二分的 "目标" 和 "边界收缩逻辑" ------ 前者是 "收敛到唯一解",后者是 "在区间内验证目标是否存在"。下面结合你给出的三个经典案例,拆解背后的核心逻辑:

先明确核心结论

循环条件 核心目标 边界收缩特点 适用场景
l < r lr 收敛到同一个索引(唯一解) 收缩时永不跳过可能的解(r=mid 找 "唯一值"(如峰值、旋转数组最小值)
l <= r 在区间内验证目标是否存在,找到则返回 收缩时可能跳过(r=mid-1/l=mid+1 找 "特定值"(如有序数组查 target)

逐个分析你的代码案例

案例 1:findPeakElement(找峰值,l < r

为什么不用 l <= r

  1. 目标是 "收敛到唯一解" :我们不需要在循环内判断 "mid 是不是峰值",而是通过趋势收缩,让 lr 最终指向同一个索引(必然是峰值)。
  2. 越界 / 死循环风险 :若用 l <= r,当 l==r 时会进入循环:
    • mid = l = r,此时判断 nums[mid] < nums[mid+1],若 mid 是数组最后一个元素,mid+1 会越界;
    • mid 不是最后一个元素,right=mid 会导致 lr 永远相等,陷入死循环。
  3. 逻辑冗余l==r 时已经找到唯一解,继续循环无意义。
案例 2:search(旋转数组查 target,l <= r
复制代码
while(l <= r) {
    int mid = l + (r - l)/2;
    if(nums[mid] == target) return mid; // 找到目标,直接返回
    // 收缩边界(跳过mid,因为mid已经验证不是target)
    if(nums[l] <= nums[mid]) {
        if(nums[mid] > target && target >= nums[l]) r = mid - 1;
        else l = mid + 1;
    } else {
        if(nums[mid] < target && target <= nums[r]) l = mid + 1;
        else r = mid - 1;
    }
}
return -1; // 区间为空,未找到

为什么必须用 l <= r

  1. 目标是 "验证存在性" :我们需要在循环内逐个验证 mid 是否是目标值,找到则返回,没找到则收缩区间。
  2. 边界收缩逻辑要求 :每次收缩都会跳过 midr=mid-1/l=mid+1),因为 mid 已经验证不是 target。若用 l < r,会漏掉最后一个可能的解:
    • 比如数组 [1],target=1,l=0, r=0l < r 不成立,直接返回 - 1,错误;
    • l <= r 会进入循环,mid=0,找到 target 返回 0,正确。
  3. 循环终止条件合理 :当 l > r 时,说明区间内所有元素都验证过,没有 target,返回 - 1 即可。
案例 3:findMin(旋转数组找最小值,l < r
复制代码
while(l < r) {
    int mid = l + (r - l)/2;
    if(nums[mid] > nums[r]) {
        l = mid + 1; // 最小值在右侧,mid不可能是解,跳过
    } else {
        r = mid;     // 最小值在左侧(含mid),mid可能是解,不跳过
    }
}
return nums[l]; // l==r,就是最小值

为什么不用 l <= r

  1. 目标是 "收敛到唯一解" :旋转数组的最小值是唯一的,我们通过比较 nums[mid]nums[r] 收缩区间,最终让 lr 指向最小值的索引。
  2. 逻辑冗余 / 死循环 :若用 l <= r,当 l==r 时进入循环,mid=l=rnums[mid] > nums[r] 不成立,r=mid 导致循环永不退出。
  3. 效率更高l < r 循环次数更少,收敛到唯一解后直接返回,无需额外判断。

通用判断法则(记住这 2 点)

什么时候用 l < r

满足以下任一条件即可:

  1. 目标是找 "唯一值"(如峰值、旋转数组最小值、有序数组找第一个 / 最后一个元素);
  2. 边界收缩时不跳过可能的解 (即有 r=mid,而非 r=mid-1);
  3. 最终返回 l(或 r (因为 l==r)。
什么时候用 l <= r

满足以下任一条件即可:

  1. 目标是 "查找特定值是否存在"(如有序数组查 target、旋转数组查 target);
  2. 边界收缩时跳过已验证的 mid (即 r=mid-1l=mid+1);
  3. 循环内有 "找到目标直接返回" 的逻辑,最终可能返回 - 1(未找到)。

补充:易混点对比

对比项 l < r l <= r
循环终止时 l == r(区间长度为 1) l > r(区间为空)
收缩方式 至少保留 mid(r=mid 必跳过 mid(r=mid-1/l=mid+1
返回值 循环外返回 l/r 循环内返回目标,循环外返回 - 1
典型场景 找唯一极值 / 边界值 找特定值(存在性验证)

总结

二分循环条件的选择,本质是匹配 "目标" 和 "边界收缩逻辑"

  • 若想让区间收敛到唯一索引(找极值 / 唯一值),用 l < r
  • 若想在区间内验证目标是否存在(找特定值),用 l <= r
cpp 复制代码
    int findMin(vector<int>& nums) {
        int l=0,r=nums.size()-1;
        
        while(l<r)//为什不是<=,因为总会出现l == r,如果是<=满足条件进入循环会一直循环下去死循环
        {//其实l == r 就确定是唯一一个值,就找到了要寻找的值
            int mid = l + (r-l)/2;
            if(nums[mid]>nums[r])
            {
                l=mid+1;
            }else{
                r=mid;//或者更直接的说法是<=的情况每次更新左右区间的时候l=mid+1,r=mid-1
            }//跳过了mid的值的判断,最后l == r的时候 mid=l=r,要进入循环才能将mid的位置的数进行判断
        }
        return nums[r];//r==l
    }
相关推荐
无尽的罚坐人生2 小时前
hot 100 283. 移动零
数据结构·算法·双指针
永远都不秃头的程序员(互关)2 小时前
C++动态数组实战:从手写到vector优化
c++·算法
水力魔方3 小时前
武理排水管网模拟分析系统应用专题5:模型克隆与并行计算
数据库·c++·算法·swmm
蜂蜜黄油呀土豆3 小时前
Redis 底层实现深度解析:从 ListPack 到哈希表扩容
数据结构·redis·zset·sds·listpack·哈希表扩容
谈笑也风生4 小时前
经典算法题型之排序算法(三)
java·算法·排序算法
郝学胜-神的一滴5 小时前
Linux进程与线程控制原语对比:双刃出鞘,各显锋芒
linux·服务器·开发语言·数据结构·c++·程序人生
大佬,救命!!!5 小时前
对算子shape相关的属性值自动化处理
python·算法·自动化·学习笔记·算子·用例脚本·算子形状
高山上有一只小老虎5 小时前
小红的推荐系统
java·算法
冰西瓜6005 小时前
贪心(一)——从动态规划到贪心 算法设计与分析 国科大
算法·贪心算法·动态规划