• 二分的神奇边界


    二分的思想主要分三种:

    1. l和r代表的“成本值”均可行,且有一个ans变量记录当前的最优
    2. l和r代表的“成本值”均可行,最后的答案是l或r
    3. l代表的“成本值”可行,r不可行,最后的答案是l(不推荐使用,比较容易出错)

     

    1、记录答案法

    while (l<=r)
    {
        int mid=(l+r)/2;
        if (judge(mid))
        {
            ans=mid;
            r=mid-1;
        }
        else l=mid+1;
    }
    printf("%d",ans);
    分析:
    judge(mid)是一个bool函数,返回mid是否可行,下面就是分情况讨论:

    <1>mid可行,此时记录ans=mid,也就是mid是目前最优解,然后缩小查找范围:r=mid-1,寻找有没有更小的解;
    <2>mid不可行,此时令l=mid+1,找更大的解来满足条件;
    总结: mid可行,就记录mid,然后找有没有更优的,否则就退而求其次,牺牲数据的优秀度来满足条件

    举例:要找出能够当飞行员的成绩最差的候选人,测试成绩居中的人,看他能不能胜任。

              <1>可以,记下他,然后对比他弱的人进行测试(ans=mid r=mid-1);

              <2>不行,对比他强的人进行上述操作(l=mid+1);

    2、不记录法

    while (l<r)
    {
        int mid=(l+r)/2;
        if (check(mid)) r=mid
        else l=mid+1;
    }
    printf("%d",l); //r也可以
    分析:

    当l==r时循环停止,此时无论输出l还是输出r都可以;取中点不讲。这里我们要保证l和r都是暂时可行的(没有证明不可行)的,

    所以当mid被判定为可行时,r应该取到mid而不是mid-1,因为这里没有ans来记录,如果r取了mid-1,我们就永远丢失了mid这个可行解(mid永远取不到了),从而导致答案错误。
    注意:l和r都要暂时可行且l要极小,r要极大
    举例:

    *打个比方:一群选手按实力顺序排好,然后我们要找出能够AK IOI的实力最差的选手,那么我们选实力居中的那个,测试他能不能AK IOI:

    
    
    1. 可以(check(mid)=true)。那么实力比他更强的选手可以回去了,因为他们一定能AK IOI,而且实力比刚刚测试的那位要高。但是那个测试过的就可以“暂时安全”,因为他是目前最优解(能AK且实力最差),这一个操作就是r=mid
    2. 不能(check(mid)=false)。那么他以及实力比他弱的全部淘汰,这个操作是l=mid+1
      然后用新的区间不断重复即可
    
    

    注:此处满足单调性,就是说如果X能AK IOI,那么比他强的也一定能,否则比他弱的一定不能**

    
    

    再举个例子:

    
    

    要找不能AK IOI的最强选手(注意,题设相反),那么照样按实力排好,测试居中的那个:

    
    
    1. 可以(check(mid)=false)。此时他以及比他更强的都不能符合要求(我们要找不能AK的,不是AKer),所以r=mid-1
    2. 不能(check(mid)=true)。此时比他弱的人全部淘汰(比他弱的更不能,而且实力比他弱),这个操作是l=mid
      之后就是对新的区间进行一样的操作
    
    

    但是这个例子要注意一个特殊情况,如果l=2,r=3,mid=5/2=2,此时测试的check(mid)如果为true,那么l和r将永远卡在2和3,所以在此处,我们取的中点要靠右,也就是(l+r+1)/2,这样就能保证l和r不死循环

     

    
    

    二分查找

       精确查找一个元素

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

    1>查找大于等于/大于key的第一个元素   

    int l=1,r=n;
    while (l<r)
    {
        int mid=(l+r)/2;
        if (a[mid]>=key) r=mid; //如果要求大于,可以将=去掉
        else l=mid+1;
    }
    return l;

    2>查找小于等于/小于key的最后一个元素

    int l=1,r=n;
    while (l<r)
    {
        int mid=(l+r)/2;
        if (a[mid]<=key) l=mid; //如果求小于,可以去掉=
        else r=mid-1;
    }
    return l;

     

     

     


  • 相关阅读:
    leetcode_697. 数组的度
    645. 错误的集合
    leetcode_448. 找到所有数组中消失的数字
    leetcode_628. 三个数的最大乘积
    leetcode_414. 第三大的数
    leetcode_495. 提莫攻击
    leetcode_485. 最大连续1的个数
    在 Mac、Linux、Windows 下Go交叉编译
    Goland基本操作
    etcd搭建及基本使用
  • 原文地址:https://www.cnblogs.com/Lemon1234/p/11606499.html
Copyright © 2020-2023  润新知