这两天又碰到了用到了二分法的题,但是由于我之前学的不是很清楚,许多二分的题都是凭感觉写出二分格式,然后WA之后凭感觉调整(竟然还能A过!!),因此再次碰到之后就感觉还是需要总结一下,要不然以后太浪费时间了。
我们用套路的方法来二分:
对于一个区间[l,r],满足 循环不变式:
arr[l]<=key arr[r]>=key
对于区间[l,r]里面存在元素则表示还在区间内,根据不同的状态,
判断 arr[mid]==key 时的区间缩减状态
1.对于查找目标值的二分查找
满足循环不变式:arr[l]<=key&&arr[r]>=key
key在区间[l,r]中,
arr[mid]==key时返回mid即可
若最后区间[l,r]中没有元素,则说明key不再数组中,返回-1即可
int search(int *arr,int n,int key)
{//在一个排好序的数组中找到key
int l=0,r=n-1,mid;
while(l<=r)//结束时,[l,r]数组为空
{
mid=(l+r)>>1;
if(arr[mid]==key)
return mid;
else if(arr[mid]>key)//key只会在 l~mid 区间,且mid不会是key,因此为 l~mid-1
r=mid-1;
else
l=mid+1;//key只会在 mid~r 区间,且mid不会是key,因此为 mid+1~r
}
return -1;//没有找到
}
例题:HDU-3763-CD 题解:https://blog.csdn.net/qq_40482358/article/details/81711039
HDU-2141-Can you find it? 题解:https://blog.csdn.net/qq_40482358/article/details/84716100
2.查找第一个与key相等的元素的位置
满足循环不变式:arr[l]<=key&&arr[r]>=key
key在区间[l,r]中,
arr[mid]==key时:
因为要查找第一个与key相等的元素,因此此时mid可能不是第一个与key相等的元素,因此区间缩小[l,mid-1]
当区间中没有元素的时候,此时必定 r<l ,
当l==r的时候,如果此时arr[mid]==key,l不变,r-1,答案为l。
如果最后一次 l!=r ,则r=l+1,
若arr[mid]==key,mid==l所以结果是l
若arr[mid]<key,转成了l==r状态
int search_first_key(int *arr,int n,int key)
{//找第一个与key相等的元素的位置
int l=0,r=n-1,mid;
while(l<=r)// [l,r]数组为空
{
mid=(l+r)>>1;
if(arr[mid]>key)//此时key必定在 [l,mid-1] 区间
r=mid-1;
else if(arr[mid]<key)//此时key必定在 [mid+1,r] 区间
l=mid+1;
else//此时key==mid,mid可能是答案区间,因此答案为[l,mid]区间
r=mid-1;
}
if(l<n&&arr[l]==key)//最后出来的时候,l必定在r的右边
return l;
return -1;
}
3.查找最后一个与key相等的元素的位置
满足循环不变式:arr[l]<=key&&arr[r]>=key
key在区间[l,r]中,
arr[mid]==key时:
因为要查找最后一个与key相等的元素,因此此时mid可能不是最后一个与key相等的元素,因此区间缩小[mid+1,r]
当区间中没有元素的时候,此时必定 r<l ,
当l==r的时候,如果此时arr[mid]==key,满足循环不变式:arr[l]<=key&&arr[r]>=key ,r不变,l+1,答案为r。
如果最后一次 l!=r ,则r=l+1,
若arr[mid]<key,满足循环不变式:arr[l]<=key&&arr[r]>=key,mid==l所以结果是r
若arr[mid]==key,转成了l==r状态
int search_last_key(int *arr,int n,int key)
{//找出最后一个与key相等的元素的位置
int l=0,r=n-1,mid;
while(l<=r)
{
mid=(l+r)>>1;
if(arr[mid]>key)//显而易见,key在[l,mid-1]位置
r=mid-1;
else if(arr[mid]<key)//显而易见,key在[mid+1,r]位置
l=mid+1;
else//key在[mid,r]位置
l=mid+1;
}
if(r>=0&&arr[r]==key)//最后一个,r在l的左边
return r;
return -1;
}
例题:HDU-6383-p1m2,题解:https://blog.csdn.net/qq_40482358/article/details/81709166
POJ-1064-Cable master,题解:https://blog.csdn.net/qq_40482358/article/details/81709228
HDU-hdu-1969-pie, 题解:https://blog.csdn.net/qq_40482358/article/details/79344461
POJ-3258-River Hopscotch 题解:https://blog.csdn.net/qq_40482358/article/details/81712748
HDU-2199-Can you solve this equation? 题解:https://blog.csdn.net/qq_40482358/article/details/84713192
4.查找第一个大于等于key的元素的位置
满足循环不变式:arr[l]<=key&&arr[r]>=key
key在区间[l,r]中,
arr[mid]==key时:
因为要查找第一个大于等于key的元素,因此此时mid可能不是第一个与key相等的元素,因此区间缩小[l,mid-1],
如果此时arr[r]<key,那么最后结束的时候必定r一直不变,l在r+1,即mid处 。
当区间中没有元素的时候,此时必定 r<l ,
当l==r的时候,如果此时arr[mid]==key,满足循环不变式:arr[l]<=key&&arr[r]>=key ,l不变,r-1,答案为l。
如果最后一次 l!=r ,则r=l+1,
若arr[mid]<key,转成了l==r状态
若arr[mid]==key,满足循环不变式:arr[l]<=key&&arr[r]>=key,mid==l所以结果是
l
int search_morefirst(int *arr,int n,int key)
{//找第一个大于等于key的元素
int l=0,r=n-1,mid;
while(l<=r)
{
mid=(l+r)>>1;
if(arr[mid]>key)//显而易见,key在[l,mid-1]区间
r=mid-1;
else if(arr[mid]<key)// key在[mid+1,r]区间
l=mid+1;
else
r=mid-1;// key在[l,mid]区间
}
return l;// l不变
}
5.查找第一个大于key的元素的位置
满足循环不变式:arr[l]<=key&&arr[r]>=key
key在区间[l,r]中,
按照循环不变式很容易就能推出过程
int search_more_frist(int *arr,int n,int key)
{//找第一个大于key的元素的位置
int l=0,r=n-1,mid;
while(l<=r)
{
mid=(l+r)>>1;
if(arr[mid]>key)// key在[l,mid-1]区间
r=mid-1;
else if(arr[mid]<key)// key在[mid+1,r]区间
l=mid+1;
else
l=mid+1;// key在[mid+1,r]区间
}
return l;//
}
例题:HDU-5101-Select 题解:https://blog.csdn.net/qq_40482358/article/details/84637559
6.查找最后一个小于等于key的元素的位置
满足循环不变式:arr[l]<=key&&arr[r]>=key
key在区间[l,r]中,
arr[mid]==key时:
因为要查找最后一个小于等于key的元素,因此此时mid可能不是最后一个与key相等的元素,因此区间缩小[mid+1,r],
如果此时arr[l]>key,那么最后结束的时候必定l一直不变,r在l-1,即mid处 。
当区间中没有元素的时候,此时必定 r<l ,
当l==r的时候,如果此时arr[mid]==key,满足循环不变式:arr[l]<=key&&arr[r]>=key ,r不变,l-1,答案为r。
如果最后一次 l!=r ,则r=l+1,
若arr[mid]<key, 满足循环不变式:arr[l]<=key&&arr[r]>=key,mid==l所以结果是r
若arr[mid]==key,转成了l==r状态
int search_morelast(int *arr,int n,int key)
{//找最后一个小于等于key元素的位置
int l=0,r=n-1,mid;
while(l<=r)
{
mid=(l+r)>>1;
if(arr[mid]>key)// key在[l,mid-1]区间
r=mid-1;
else if(arr[mid]<key)// key在[mid+1,r]区间
l=mid+1;
else
l=mid+1;// key在[mid,r]区间
}
return r;//最后在r处取得最后以一个小于等于key的元素
}
例题:HDU-5101-Select 题解:https://blog.csdn.net/qq_40482358/article/details/84637559
7.查找最后一个大于key的元素的位置
满足循环不变式:arr[l]<=key&&arr[r]>=key
key在区间[l,r]中,
按照循环不变式很容易就能推出过程
int search_morelast(int *arr,int n,int key)
{//找最后一个小于key元素的位置
int l=0,r=n-1,mid;
while(l<=r)
{
mid=(l+r)>>1;
if(arr[mid]>key)// key在[l,mid-1]区间
r=mid-1;
else if(arr[mid]<key)// key在[mid+1,r]区间
l=mid+1;
else
r=mid-1;// key在[l,mid-1]区间
}
return r;//最后在r处取得最大的小于key的元素
}
未完待续......
这么系统的总结肯定不是我先写的啊,下面是参考的不知名大佬的博客:
https://blog.csdn.net/ebowtang/article/details/50770315
以后碰到其他的状态的话会继续补充的。