二分法及其边界问题、自锁问题
作者:互联网
本文是受到了【洛谷日报#13】浅谈二分的边界问题的启发后写出来记录一下的,如有需要还是建议先去看这篇洛谷日报。
二分法
曾经有位大犇向我们传授道:看到求最大的最小或是最小的最大,那么就大概率是二分答案。
举一个猜数字的例子:你的朋友心中默想1到n中的一个数让你去猜,而在你每次猜出后,他会用“大了”或是“小了”来作为回应,直到你猜到那个数字为止。
最容易想到的就是暴力的策略: 你从1开始连续的向后猜,直到猜到为止,这种方法的效率是O(n)。
之后就是要说的二分答案的策略:首先我们猜最中间的那个数(1+n/2),如果大了,说明数在比这个中间数小的左边那一半,反之如果小了,便是在右边更大的数那一半。
在之后,我们便再去取剩下的那半边的中值就好。可以看到我们每猜一次,便能排除掉现有的一半的数,直到猜出答案,这种方法的效率是O(logn)。
二分法的边界问题
在逛博客的时候,看到好多人将边界问题分为啥开区间闭区间之类的,这让数学能力不强的我感到很难读懂。我来说一下我的理解。
在处理边界问题时,只需要注意一点就是要按照题目进行分析,而不是盲目的去套用不同情况下总结的模板。以上题为例:
l=1,r=n;
while(l<r)
{
mid=(l+r)>>1;
if(mid>x) r=mid-1;
if(mid<x) l=mid+1;
if(mid==x)
{
printf("就是它了");
return 0;
}
}
printf("l和r都是答案");
在初学时,我对于最终的答案应该是l还是r还是mid非常不确定,所以可以使用一种笨方法就是都单独去判断一遍(我就是这么过来的)。
我们看到循环条件l<r,这说明循环在l==r时停止,也就是找到了那个值,所以当循环结束时,l和r都是答案的值。
而mid不是,因为当l==r时退出了循环,mid来不及做本轮的赋值操作,它还是上一轮循环的mid的值。
mid>x:这说明当前的值比答案“大了”,因此可以排除掉包括mid以及其右边的数(因为mid本身已经大了),所以为r=mid-1;
mid<x:这说明当前的值比答案“小了”,因此可以排除掉包括mid以及其左边的数(因为mid本身已经小了),所以为l=mid+1;
mid==x:这说明这就是答案,输出就好了。
接下来,我们稍微变换一下写法:
l=1,r=n;
while(l<r)
{
mid=(l+r)>>1;
if(mid>=x) r=mid;
if(mid<x) l=mid+1;
}
printf("l和r都是答案");
mid>=x:这说明当前的值可能比答案“大了”,因此可以排除掉不包括mid的其右边的数(因为mid本身可能就是答案),所以r=mid;
所以说,r和l的赋值并没有什么固定的写法,而是根据题意和自己的写法分析得出的,这是我想说明的很重要的事。
自锁问题(不确定,酌情参考)
二分法往往会遇到自锁的问题,也就是l和r永远碰不到一起导致无限循环。我们把栗子再换一种写法:
//会自锁!!
l=1,r=n;
while(l<r)
{
mid=(l+r)>>1;
if(mid>x) r=mid-1;
if(mid<=x) l=mid;
}
printf("l和r都是答案");
表面看上去没有影响,但我们考虑一组值:x=2,l=2,r=3,此时mid=(2+3)/2=2,mid仍等于2,导致执行l=mid=2,可以发现l和r永远的卡在了2和3。
我的理解是这样的:
计算机在进行除法运算时,会自行抹去整型后边小数的部分,例如(5+6)/2=5,因此我们可以说,(l+r)/2这种操作所得到值是偏小的,而我们也恰巧在mid
偏小的同时将等于x的情况合并纳入了mid<x中,这就导致了有些情况下明明mid偏小了不是正确答案,而有l=mid的存在使得左边界无法移动导致自锁。
解决的办法也很简单,既然是这么归纳的条件,那么让mid的值偏大即可,也便是mid=(l+r+1)>>1。
同理,在mid=(l+r+1)>>1的情况下:
//会自锁!!
l=1,r=n;
while(l<r)
{
mid=(l+r+1)>>1;
if(mid>=x) r=mid;
if(mid<x) l=mid+1;
}
printf("l和r都是答案");
在一组值:x=3,l=2,r=3时,同样会自锁。
自锁情况的理解仅供参考,我也不确定是不是还有别的锁住的情况。
标签:mid,边界问题,二分法,while,答案,自锁 来源: https://www.cnblogs.com/CYHeimu/p/15319100.html