关于几种二分的想法

一般的二分能解决在单调序列中查找某个数是否存在的作用,假如查找的某数有多个,我要你输出第一个的位置,或者最后一个的位置,其实这些问题也能用而二分解决。
c++中貌似自带lower_bound 和upper_bound函数,分别是在单调有序的序列中,找到第一个大于给定值和第一个大于等于给定值的数的位置,但是这些都是怎么实现的呢?
现在有一串单调非递减的序列(意味着有重复数字),那么先说明4个问题:
1 找出这个序列第一个大于给定数字k的位置和那个数,这个数可能出现了好几次,我要找第一次的位置;
2 找出这个序列第一个大于等于给定数字k的位置和那个数,这个数可能出现了好几次,我要找第一次的位置;
3 找出这个序列第一个大于给定数字k的位置和那个数,这个数可能出现了好几次,我要找最后一次的位置;
4 找出这个序列第一个大于等于给定数字k的位置和那个数,这个数可能出现了好几次,我要找最后一次的位置;
3,4两个问题可能表述的不够清楚,这里的意思是,假如这串数中从小到大第一个满足条件的数有好多个,或者说出现了很多次,我要求的是第一个符合条件的最后一次出现的,并不是整串中的最后一个,比如1,2,2,2,3 ,我要找最后一个(第一次)大于等于2的位置,我要求的不是3这个位置,而是最后一个2的位置。
对于问题1,有如下代码:

while(l<r)
{
   int mid=l+(r-l)/2;
   if(a[mid]>k)
      r=mid;
   else if(a[mid]<=k)
      l=mid+1;
}


对于问题2,有如下代码:

while(l<r)
{
    int mid=l+(r-l)/2;
    if(a[mid]>=k)
       r=mid;
    else if(a[mid]<k)
       l=mid+1;
}

最后跳出循环时l=r就是所求的位置。
两者的差距就是取到等号的时候执行的不同的语句,一般来说二分最怕的是死循环,我认为可以从边界考虑,比如从l+1==r,l+2==r,l+3==r这三种情况,会发现这三种情况的任意一种都不会导致死循环,而且第三章情况会变成第一种情况,并且退出循环时,l==r。
那对于问题3和问题4,我们不能直接解决它,但是可以通过二分找两次来解决,
对于问题3,我们先找到第一个大于K的数的位置,设为pos,然后我们再找第一个大于a[pos]这个数的位置,
设为pos2,然后pos2-1就是答案了。
对于问题4,我们先找到第一个大于等于K的数的位置,设为pos3,然后在找第一个大于a[pos3]这个数的位置,
设为pos4,然后pos4-1就是答案了。
其实还有4个类似的问题,现在要找出最后一个出现小于(等于)K的数的第一个位置和最后一个位置。
也可以用差不多的方法解决。
接着来考虑问题5到问题8,
问题5找出这个序列最后一个小于给定数字k的位置和那个数,这个数可能出现了好几次,我要找最后一次的位置;
问题6找出这个序列最后一个小于等于给定数字k的位置和那个数,这个数可能出现了好几次,我要找最后一次的位置;
问题7找出这个序列最后一个小于给定数字k的位置和那个数,这个数可能出现了好几次,我要找第一次的位置;
问题8找出这个序列最后一个小于等于给定数字k的位置和那个数,这个数可能出现了好几次,我要找第一次的位置;
问题5和问题6,其实蛮简单的,只需要将问题2和问题1的位置减去1即可。
即问题2答案-1=问题5答案,问题1答案-1=问题6答案。
对于问题7,可以先找到第一个大于等于K这个数的位置,记为pos5,
然后我再找第一个大于等于a[pos5-1]的位置pos6即为答案。
对于问题8,可以先找到第一个大于K这个数的位置,记为pos7,
然后我再找第一个大于等于a[pos7-1]的位置pos8即为答案。
至此所有问题都解决,二分真是一个给力的东西!
最后还需要说明的一点,我这种二分的写法没办法处理一直特殊情况 ,为了方便理解,之前也没有说明,我代码中的l和r一直代表所能取到的数的闭区间,但是会有有可能,我要找的数它一开始就不在这个区间,比如1,2,3,4,5,我要找第一个大于6的数的位置,我初始化l=0,r=4那么无论如何这个求解结果都是错的,因为根本不存在0到4的范围里有这个数。我想到的一种做法就是,初始化r=5,那么答案是正确的。r初始化的位置本来选取最后一个数的位置,但是为了保证结果的准确,可以初始化为最后一个数的位置+1。

你可能感兴趣的:(关于几种二分的想法)