二分法的精髓——边界

二分法朦胧面纱下的神秘真相~

  • 二分法相信很多童鞋都有所了解,但是只要自己亲自动手编写过使用二分法的程序,相信大部分人都为边界问题困扰过,其实二分法的边界处理是有玄机滴~
  • 如果读者只是想熟练地掌握二分法的编程框架,能够在竞赛、面试等场合手到擒来,那本篇博客保证够用。
  • 而若读者是第一次遇到边界问题,想要弄清楚她的神秘面纱下的所有细节,可以阅读下面这篇文章:浅谈二分的边界问题
  • 笔者只是想记录一下自己的思考,方便日后复习和使用。

二分法边界问题最简单易懂且高效的框架

网上关于二分法边界处理的讲解文章有很多,其实无非分为三种策略,这里笔者只介绍其中最经典最易懂且最高效的一种框架,根据两种可能出现的情况灵活变换mid的取值方式和l、r的收缩方式即可。需要记住的只有以下三条原则:

  1. 保证答案一定在[ l, r ]中
  2. l, r需要不断收缩靠近(此点关系到mid的取值方式)
  3. 当l == r时,l和r就是正确答案

是不是觉得很简单,但又好像差那么点意思,且看下文。

第一种情况:求“满足条件”的最小值

举例:

把一些会玩王者荣耀的小朋友按照水平由低到高排好序,想要从中找到能够打败小明的水平最差的那个小朋友。(这里假设水平高一些的一定可以打败水平低一些的。。)

代码如下:

while(l < r){
    mid = (l+r) >> 1; //右移1位,即除以2
    if(check(mid)) r = mid;
    else l = mid + 1;
}

其中check(mid)是一个返回值为bool类型的函数,当序号为mid的小朋友可以打败小明时,返回为true;否则返回false。
我们仔细分析一下这几行代码,首先是第一条原则:答案一定在[ l,r ]中

  • 当check(mid)为true时,说明mid可以打败小明,那么序号大于mid的一定也可以,而题目要找的是能打败他的水平最差的,所以正确答案一定在序号小于等于mid(可能是mid)的一侧,因此令r=mid而不是r=mid-1
  • 当check(mid)为false时,说明mid已经无法打败小明,那么序号小于等于mid的小朋友都无法打败小明,正确答案一定在[mid+1,r]这个区间内,所以置l=mid+1,而不是l=mid

怎么样,这么一细说,是不是立刻豁然开朗了~
别急,第二条原则还暗藏玄机:l、r要能不断靠近
为了介绍第二条原则,先讲一下第二种情况:

第二种情况:求“满足条件”的最大值

同样举例:

把一些会玩王者荣耀的小朋友按照水平由低到高排好序,想要从中找到不能打败小明的水平最高的那个小朋友。(这里仍然假设水平高一些的一定可以打败水平低一些的)

这个时候,我们就要变换代码框架为如下格式:

while(l < r){
    mid = (l+r+1) >> 1; //右移1位,即除以2
    if(check(mid)) r = mid - 1;
    else l = mid;
}

注意其中的改变了的地方,我们来分析为什么要变为这样,首先仍然是第一条原则:答案一定在[ l,r ]中

  • 当check(mid)为true时,说明mid可以打败小明,那么序号大于mid的一定也可以,而题目要找的是不能打败他的水平最高的,所以正确答案一定在序号小于mid(不包括mid)的一侧,因此令r=mid-1而不是r=mid
  • 当check(mid)为false时,说明mid已经无法打败小明,那么序号小于等于mid的小朋友都无法打败小明,但是我们又要找不能打败他的水平最高的那个,所以正确答案一定在[mid,r]这个区间内(可能是mid),所以置l=mid,而不是l=mid+1

ok,到这里关于第一条原则相信大家已经可以自己想得很清楚了,我们再来说第二条原则:l、r要能不断靠近,注意这里我们变换了mid的取值方式为向上取整,即:mid = (l+r+1) >> 1

我们先来看一下,如果仍然按照向下取整的方式,在第二种情况对应的框架下,会发生什么:

  • 如果l、r在某轮循环中分别是2、3,则mid=2,若check(mid)为false,则l=mid,你会发现,咦?此轮循环后,l和r的值都没变,不仅如此,往后每一次循环它们都不会再变了!死循环他来了!

所以要变化mid的取值方式,关于mid的取值方式其实大家不必过于纠结,只要它能一直取到[l,r]中的一个点,并且保证l、r能不断靠近即可,不用在意到底是不是中点,只是相对来说取尽量靠近中间的点收敛得更快一些而已。

不仅是第二种情况对应的框架选用不正确的mid取值方式可能会陷入死循环,第一种也会,如果我们让第一种框架中的mid取值方式改为mid = (l+r+1)>>1,同样存在陷入死循环的可能,仍然用上面2、3的例子,此时mid=(2+3+1)>>1,即mid=3,而如果check(3)为true,则r=mid=3……

相信大家已经看出来规律了,如果是r=mid,则mid应该向下取整;如果是l=mid,则要向上取整,就这么简单。实在记不住,现场用2、3推理一下,一分钟就搞定啦。

okok,啰嗦了这么多,终于到第三条原则了:当l == r时,l或r就是正确答案
这一条没有什么玄机,只是提醒读者,循环的条件就是l < r,无论情况怎么变,都不用管它,也不要改它为l<=r或者其他的什么。道理很简单,我们已经保证了答案一定在[l , r]中,l==r时,这个区间就那一个点,所以l和r就是答案啦~

总结

总而言之,二分只需要保证以下三点原则:

  1. 保证答案一定在[ l, r ]中,要根据题目要求(最大值还是最小值),变换l和r的收缩方式。
  2. l, r需要不断收缩靠近,同样要根据题目要求(最大值还是最小值),改变mid的取值方式(需要r=mid时,就令mid向下取整,需要l=mid时,就令mid向上取整)
  3. 当l == r时,l或r就是正确答案,无需变换循环条件(一直是l<r)

Mark一个好用的表情网站✌️

  • 7
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值