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

来源:互联网 发布:linux编译环境搭建 编辑:程序博客网 时间:2024/06/05 20:47

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

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

  • 上下限的确定,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在哪行,然后对这一行用最基本的二分查找即可
类似的题目还有很多

原创粉丝点击