二分搜索

二分搜索看起来简单,但在实际运用中却变化万千,容易出错。博主之前也是看到一遍就去学习查找,然而并没有进行总结,在边界条件,返回值等总是模糊,后来想着还是彻底把它搞清楚,这样一劳永逸,同时也能够灵活运用。这是这个专题的初衷。

二分搜索或者说二分查找应该注意的几个点:

  1. 区间的开闭 左闭右闭 还是左闭右开

  2. 循环条件 left<right 还是 left<=right 实际和第一点是呼应的

  3. 更新条件 左右区间的更新,同样要对应开闭原则,维持区间的循环不变性

  4. 返回值 返回left right 还是需要再判断

  5. 搜索条件 大于 等于 小于 大于等于 小于等于

    对此进行了总结并给出了实例。首先明确一个方向问题,当寻找一个第一个大于等于t的数时,我们会从左向右进行搜索,如果开始mid就符合条件,即arr[mid]>=t 那么更新条件就是往这个方向的反方向,即回退,right应该更新,如果是闭区间 right=mid-1(不会错过这个数,如果mid就是第一个大于等于的数,最终循环退出会有left=right+1,此时right再去搜索时right的位置不会变,left会更新,并且left>right退出,回到原先mid的位置,返回right即可,如果arr[mid]<t,说明[0,mid]都不符合条件,向右搜索,left=mid+1

 /**
     * 总结:
     * 1)开闭区间 统一闭区间 [left,right]  left=0;right=arr.length-1;   如果用开区间[left=0,right=n) 那么下次递推的时候也要如此,类似数学归纳法
     * 2)while循环退出条件  while(left<=right)  退出时有left>right  left=right+1;
     * 3)条件  大于等于    小于等于  等于  第一个   最后一个  方向
     *   首先确定方向原数组默认从小到大,  给出条件,如第一个大于等于t的数,则方向为从左到右,循环语句如下
     *   if(arr[mid]>=t)  //命中条件,这时再结合方向和所给出的条件做判断
     *   {
     *       //第一个大于等于  方向为从左到右,我们此时命中的条件 并不能保证是第一个,因此回退,往反方向(从右向左,去寻找第一个)走,即right指针回退
     *       right=mid-1;
     *   }else{
     *      //没有触发条件,需要进一步收缩空间
     *       left=mid+1;
     *   }
     *  4)循环不变量  我们要找的数,如果存在则始终是在[left,right]这个闭区间中
     *  5)循环退出返回
     *      循环退出要么是在区间内找到了这个数,要么是整个数组中就不存在这个数,需判断
     *      注意方向和上下界溢出的问题  方向指的是从哪个方向找到满足条件的第一个数的方向
     *      如果是从左往右(大于等于)那么返回left,这时需要注意上界,即left==arr.length,这种情形,right一直向左逼近
     *      如果是从右向左(小于等于)那个返回right,这时需要注意下界,即right==0; left会向右逼近,以至于越界
     *
     *      另外对于找只是等于的问题  可以转换为大于等于  或者小于等于, 只是在返回的时候需要注意,所找的数是否在数组中
     *      对于大于等于    return (left==arr.length || arr[left]==t)?-1:left;
     *      对于小于等于   return (right==0 || arr[right]==t)?-1:right;
     *
     * 6)给出条件 就看arr[mid]是否触发条件,再移动方向,不外乎两种  left=mid+1,right=mid-1,
     * 看具体往哪个方向走,触发条件往反向走,
     * 未触发继续将区间进行收敛
     *
     *
     * !初始区间的表示法在选定后, 两条边界更新语句应该与之对应,否则可能会出现死循环
     *其实死循环的导致,是因为区间的左右边界条件全部都写错了(也可以说是前面第一种错误情况的一种特例,第一种错误情况是只有   左边界或者右边界写错)
     *
     *
     * 对于开区间  大于等于返回left   小于等于返回right-1     此时退出条件有left==right
     * 循环条件while (left < right)
     *  更新条件right = mid;
     *  left = mid + 1;
     *
     */

下面给出各种实例,包括区间不同,搜索方向不同等。

 /**
     * 查找数组中与target相等的数,返回索引,没有找到返回-1
     * @param nums
     * @return
     */
    public static int binaryFind(int[] nums, int target) {
        int left = 0, right = nums.length - 1;
        //取闭区间,在[left,right]中去寻找这个数,区间作为一个循环不变量
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] == target) {
                return mid;
            } else if (nums[mid] > target) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }
        return -1;
    }

    /****************************-----------------------------------------------************************/
    /*
    0,1,1,2,2,2,4,4,5,6   t=2
    left=0 mid= 5 right= 10   命中2,arr[mid]<=t  则left=mid+1;可能此时left指向的数已经大于t了
    left=6 mid= 8 right= 10
    left=6 mid= 7 right= 8
    left=6 mid= 6 right= 7
    left=6
    right=6
    */
    //使用开区间查找第一个大于num的数
    public static int findFirstGNumOpen(int[] arr, int target) {
        int left = 0, right = arr.length;
        while (left < right) {
            int mid = left + (right - left) / 2;
            System.out.println("left=" + left + " mid= " + mid + " right= " + right);
            if (arr[mid] > target) {//触发条件
                right = mid;
            } else {
                left = mid + 1;
            }
        }
        System.out.println("left=" + left);
        System.out.println("right=" + right);
        return (left == arr.length) ? -1 : left;
    }

    /**
     left=0 mid= 5 right= 10
     left=0 mid= 2 right= 5
     left=3 mid= 4 right= 5
     left=3 mid= 3 right= 4  [3,4)
     left=3
     right=3
     二分查找第一个大于等于target=2返回下标3
     */
    public static int findFirstGENumOpen(int[] arr, int target) {
        int left = 0, right = arr.length;
        while (left < right) {
            int mid = left + (right - left) / 2;
            System.out.println("left=" + left + " mid= " + mid + " right= " + right);
            if (arr[mid] >= target) {//触发条件  与上面相比等号的位置挪上来了,右边移动了
                right = mid;
            } else {
                left = mid + 1;
            }
        }
        System.out.println("left=" + left);
        System.out.println("right=" + right);
        return (left == arr.length) ? -1 : left;
    }

    /****************************-----------------------------------------------************************/
    //使用开区间找第一个小于t的数
    public static int findFirstLNumOpen(int[] arr, int target) {
        int left = 0, right = arr.length;
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (arr[mid] < target) {//触发条件
                left = mid + 1;
            } else {
                right = mid;
            }
        }
        System.out.println("left=" + left);
        System.out.println("right=" + right);
        return (right == 0) ? -1 : right - 1;
    }

    public static int findFirstLENumOpen(int[] arr, int target) {
        int left = 0, right = arr.length;
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (arr[mid] <= target) {//触发条件
                left = mid + 1;
            } else {
                right = mid;
            }
        }
        System.out.println("left=" + left);
        System.out.println("right=" + right);
        return (right == 0) ? -1 : right - 1;
    }
    /****************************-----------------------------------------------************************/
    public static int findFirstEqualNumOpen(int[] arr, int t) {
        int left = 0, right = arr.length;
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (arr[mid] >= t) {//找到满足flag的数,
                right = mid;
            } else {
                left = mid + 1;
            }
        }
        System.out.println("left=" + left);
        System.out.println("right=" + right);
        //返回值判断,超过右边界
        return (left == arr.length || arr[left] != t) ? -1 : left;
    }


    //从右向左第一个小于等于t的数,触发条件,回退,向右,left=mid+1;没有触发,继续前进,向左,right=mid-1;
    public static int findLastEqualNumOpen(int[] arr, int t) {
        int left = 0, right = arr.length;
        while (left <right) {
            int mid = left + (right - left) / 2;
            System.out.println("left=" + left + " mid= " + mid + " right= " + right);
            //相当于从左向右找第一个小于等于t的数, 如果全部比t小不会溢出,返回最后一个元素下标,如果全部比t大,right位置在-1处
            //如果数组不存在那个数,再做一步判断,right为指向第一个小于等于t的数判断它是否和t相等
            if (arr[mid] <= t) {//找到满足flag的数,
                left = mid + 1;
            } else {
                right = mid;
            }
        }
        System.out.println("left=" + left);
        System.out.println("right=" + right);
        //返回值判断
        return (right == 0 || arr[right-1] != t) ? -1 : right-1;//注意是right-1 ******************
    }
    /****************************-----------------------------------------------************************/








    //二分查找 第一个大于num的数,没找到返回-1;
    public static int findFirstGreatNum(int[] arr, int target) {
        int left = 0, right = arr.length - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (arr[mid] > target) {//触发条件
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }
        System.out.println("left=" + left);
        System.out.println("right=" + right);
        return (left == arr.length) ? -1 : left;
    }

    //二分查找 第一个大于等于num的数,没找到返回-1;
    public static int findFirstGqNum(int[] arr, int target) {
        int left = 0, right = arr.length - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (arr[mid] >= target) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }
        System.out.println("left=" + left);
        System.out.println("right=" + right);
        return (left == arr.length) ? -1 : left;
    }


    public static int findFirstLessNum(int[] arr, int t) {
        int left = 0, right = arr.length - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (arr[mid] < t) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        System.out.println("left=" + left);
        System.out.println("right=" + right);
        //返回值判断,求第一个小于t的数,t如果比最小的数即第0个数小,就返回-1
        return (right < 0) ? -1 : right;
    }


    /**
     * 找第一个小于等于t的数
     *
     * @param arr
     * @param t
     * @return
     */
    public static int findFirstLessEqualNum(int[] arr, int t) {
        int left = 0, right = arr.length - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (arr[mid] <= t) {//找到满足flag的数,
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        System.out.println("left=" + left);
        System.out.println("right=" + right);
        //返回值判断,求第一个小于t的数,t如果比最小的数即第0个数小,就返回-1
        return (right < 0) ? -1 : right;
    }

    /**
     * 返回第一个等于t的数
     * 从左向右查找第一个大于等于t的数,因为是查找从左边起第一个大于等于t的数,因此,right会不断的向左边移动,最后right和left都
     * 指向第一个大于等于t的数时,right还会再移动,left代表返回的结果
     *
     * @param arr
     * @param t
     * @return
     */
    public static int findFirstEqualNum(int[] arr, int t) {
        int left = 0, right = arr.length - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (arr[mid] >= t) {//找到满足flag的数,
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }
        System.out.println("left=" + left);
        System.out.println("right=" + right);
        //返回值判断,超过右边界
        return (left == arr.length || arr[left] != t) ? -1 : left;
    }

    //从右向左第一个小于等于t的数,触发条件,回退,向右,left=mid+1;没有触发,继续前进,向左,right=mid-1;
    public static int findLastEqualNum(int[] arr, int t) {
        int left = 0, right = arr.length - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            //相当于从左向右找第一个小于等于t的数, 如果全部比t小不会溢出,返回最后一个元素下标,如果全部比t大,right位置在-1处
            //如果数组不存在那个数,再做一步判断,right为指向第一个小于等于t的数判断它是否和t相等
            if (arr[mid] <= t) {//找到满足flag的数,
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        System.out.println("left=" + left);
        System.out.println("right=" + right);
        //返回值判断
        return (right < 0 || arr[right] != t) ? -1 : right;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值