二分法专题-lintcode二分法问题解惑

本文探讨二分法在解决有序序列问题中的应用,特别是在Lintcode和LeetCode平台上的典型题目。内容包括最普通二分查找的上下限设定,寻找第一个或最后一个满足条件的元素,以及在不同情况下如何调整循环条件和边界处理。同时,文章通过举例说明了如何避免循环无法退出的情况,并解释了为何不需要右偏调整。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

二分法是常考的算法,适用于:在一个有序的序列中,找第一个满足某条件的位置(当然也可以找满足某条件的最后一个位置)

学习过程中让人迷惑的问题有:

  • 上下限的确定,low=0,high=n-1还是low=0,high=n
  • 循环条件low<high还是 low<=high
  • 循环中low=mid还是low=mid+1,high=mid还是high=mid-1
  • 有些情况无法退出循环,即是要mid=(low+high)/2还是mid=(low+high+1)/2    (即向左偏还是向右偏)
通过例子来帮助理解,不需要死记硬背

最普通的二分查找

int findPosition(vector<int> &nums, int target) {
        // write your code here
        //二分查找
        if(nums.size()==0) return -1;
        int low=0,high=nums.size()-1;
        int mid;
        while(low<=high){
            mid=low+(high-low)/2;
            if(nums[mid]==target) return mid;
            if(nums[mid]<target){
                low=mid+1;
            }
            else{
                high=mid-1;
            }
        }
        //循环结束,没有返回,则不存在
        return -1;
    }
  • 上下限要覆盖所有的可能,故n-1就足够了,当然扩大一些,上限为n也可以
  • 这里要找等于target的数,这样写循环结束后low>high,[low,high]无法构成区间,通过此判断是否存在,故写成low<=high

第一个满足某条件的位置,最后一个满足某条件的位置

我们将最后一个满足某条件a的位置转换为求第一个!a的位置,再-1即可,可避免一些问题

即求最后一个满足a<=sqrt(x)的数,可以直接求,也可以转化为第一个满足a>sqrt(x)的位置,然后再减1
我们先分析第二种的写法
  • low=0,上限是多少?high=x当然没错,但可以更小一些。sqrt(x)不可能超过x/2,即求sqrt(100)不会超过50,sqrt(101)不会超过50.5,但c++中除法会得到50,故x/2+1。但注意,我们现在是要求a>sqrt(x)的位置,故还要向后放一个,x/2+2。总结下:直接求a<=sqrt(x)的最后一个数,low=0,high=x/2+1;求a>sqrt(x)的第一个位置:low=0,high=x/2+2
  • 若mid*mid<=x则low=mid+1,因为我们要找的是a>sqrt(x)的第一个位置,mid*mid<=x后是取不到mid的故low=mid+1而不是low=mid,同理,mid*mid>x时,可以取到mid,high=mid
  • 是否需要右偏?不需要,因为循环条件low<high,最后low,high到了相邻位置,(low+high)/2向左偏,到了low,可以通过low=mid+1破坏循环条件,从而退出
代码如下:
int sqrt(int x) {
        // write your code here
        //将上界放为x/2+2
        long low=0,high=x/2+2;
        long mid;
        //这里尝试一种更好理解的方法:找到第一个mid*mid>x的数,再-1即可
        //一旦使用这种方法,上界需要改为x/2+2
        while(low<high){
            mid=low+(high-low)/2;
            if(mid*mid<=x){
                low=mid+1;
            }
            else{
                high=mid;
            }
        }
        //返回夹出的位置-1
        return low-1;
    }

直接求a<=sqrt(x)的最后一个位置:
区别在于上限为x/2+1,mid*mid<x则low=mid(因为这里mid可能就是<sqrt(x)的最后一个数),mid*mid>x,则high=mid-1。这里只能通过high=mid-1破坏low<high的循环条件,故应该右偏mid=(low+high+1)/2,最后注意应该返回high
所以求最后一个位置还是推荐转化为第一个位置-1

 int sqrt(int x){
         long low=0,high=x/2+1;
        long mid;
         
        while(low<high){
            //注意这里的二分,要向右偏
            //如果不向右偏,x=50,low=7,high=8,会一直循环下去;
            //向右偏,low=7,high=8,然后mid=8>50故high=7,返回7即可
            mid=low+(high-low+1)/2;
            if(mid*mid==x){
                return mid;
            }
            else if(mid*mid<x){//sqrt在右侧,注意这里可能等于mid,不应写low=mid+1
                low=mid;
            }
            else{//sqrt在左侧
                high=mid-1;
            }
        }
        //注意这里不能返回low
        return high;
    }

其他类似题目



即找第一个>=target的位置,再找第一个>target的位置-1


先对第0列的数二分,找最后一个<=target的数的位置即第一个>target的位置-1,这样就可以确定target在哪行,然后对这一行用最基本的二分查找即可
类似的题目还有很多

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值