二分查找法。Java泛型设计二分查找法。

前言基础:

1、只能针对有序的数组进行查找。

2、时间复杂度为O(logn)。

3、使用前可以先排序,然后再查找,广泛应用于当一大组数据需要频繁查找,于是我们先将其进行排序,然后再通过二分查找进行查找。

4、主要有两种实现方式:递归法和非递归法。

5、如果有两个相同的元素,二分查找法返回的永远都是下标值小的那个索引。

        例:Integer arr[]={0,1,2,3,3};找3,最终返回的是index=3而不是4.

6、二分查找法的核心就是确定边界,看好函数目的确定if条件,while循环是否取等就看取等时数组是否为空。

7、我们只要发现最后得到的结果是线性有序的,就可以考虑二分查找法解决问题(下有leetCode理题)。

8、二分查找法最终如果没有找到目标元素,l(左边界)所在的位置就是该元素应该插入的位置

leetCode35
 

class Solution {
    //1、递归实现
    public int searchInsert1(int[] nums, int target) {
        return myWay(nums,0,nums.length-1,target);
    }
    public int myWay(int[] nums,int l,int r, int target){
        if  (l>r) return l;
        int mid=(l+r)/2;
        if(nums[mid]==target)
            return mid;
        if(nums[mid]<target)
            return myWay(nums,mid+1,r,target);
        return myWay(nums,l,mid-1,target);
    }

    //2、非递归实现
    public int searchInsert(int[] arr, int target) {
        int l=0,r=arr.length-1;
        while(l<=r){
            int mid=(l+r)/2;
            if  (arr[mid]==target)
                return mid;
            if(arr[mid]<target)
                l=mid+1;
            else
                r=mid-1;
        }
        return l;
    }
}


查找过程:

        每次对数组进行划分,选取中间的元素,让中间的元素与要查找的元素进行比较,然后修改左右边界。


 代码部分:(非递归与递归算法)

public class BinarySearch {
    public BinarySearch() {
    }

    //1、非递归实现:
    public static <E extends Comparable<E>> int search(E arr[], E target) {
        int start = 0, end = arr.length - 1;

        //此处是小于等于end,每次在arr[start,end]中查找
        //while循环就是在保证里面有元素,条件都是根据循环不变量来确定的
        while (start <= end) {     
            int mid = (start + end) / 2;

            if (arr[mid].compareTo(target) == 0)
                return mid;
            if (arr[mid].compareTo(target) > 0)
                end = mid - 1;
            else
                start = mid + 1;
        }
        return -1;
    }

    //2、递归方式
    public static <E extends Comparable<E>> int search_D(E arr[], E target) {
        return search_D(arr, target, 0, arr.length - 1);
    }

    private static <E extends Comparable<E>> int search_D(E[] arr, E target, int start, int end) {
        if (start > end) return -1; //此处不取等,因为当start>end时数组为空数组,不符合条件,所以才返回-1

        int mid = (start + end) / 2;

        if (arr[mid].compareTo(target) == 0)
            return mid;

        if (arr[mid].compareTo(target) > 0)
            return search_D(arr, target, start, mid - 1);
        else
            return search_D(arr, target, mid + 1, end);
    }
}

二分搜索法的应用:

二分搜索法的一个非常重要的应用就是寻找边界问题:

1、找大于某个数的最小值,小于某个数的最大值。

2、寻找最长期限问题等。

一)寻找大于目标值的最小元素的值。

例如:有一次考试,我们要找到第一个大于六十分的同学(不包括60分)的索引,从而获得其基本信息。 

注意事项:

1、初始值问题:l为0,r为arr.length而不是最后一个元素,因为尽管数组中不存在这样的元素,也不至于没有元素可以返回。

    /**
     * 大于目标值的最小值
     */
    public static <E extends Comparable<E>> int upper(E arr[], E target) {
        //在arr[0,arr.length]中寻找
        int start = 0, end = arr.length;
        while (start < end) {           // 要保证其不是空数组,所以这里不取等
            int mid = (start + end) / 2;
            //找的是比它大的最小值,此时将等于它的忽略掉
            /**此处不取等*/
            if (arr[mid].compareTo(target) > 0)
                end = mid;
            else
                start = mid + 1;
        }
        return start;           // 最终l不会超过r,结束的条件都是l等于r。所以返回r和l都可
    }


    //派生代码:(拓展)
    //1、ciel就是向上取整的意思。如果元素存在就返回最大的索引,不存在就返回比其大的最小的元素索引。
    public static <E extends Comparable<E>> int ciel(E arr[], E target) {
        int upper = upper(arr, target);
        if (upper - 1 >= 0 && arr[upper - 1] == target)
            return upper - 1;
        return upper;
    }

    //2、如果元素存在就返回索引较小的那个元素的下标。
    public static <E extends Comparable<E>> int lower_ceil(E arr[], E target) {
        //在arr[0,arr.length]中寻找
        int start = 0, end = arr.length;  
        while (start < end) {
            int mid = (start + end) / 2;
            if (arr[mid].compareTo(target) >= 0)     
                end = mid;
            else
                start = mid + 1;
        }
        return start;
    }

二)寻找小于目标元素的最大值:

1、此处的初始化l=-1同样是为了当数组没有满足的元素的时候不至于无法返回值

2、当l=-1时,计算mid时要多加个1。

/*返回小于目标元素的最大值*/ 
public static <E extends Comparable<E>> int lower(E arr[], E target) {
        int start = -1, end = arr.length - 1;
        while (start < end) {
            int mid = (start + end + 1) / 2;/**此处要比二分搜索多加个1*/

            //因为在找小于target的最大值,所以等于的那个元素可以不要
            if (arr[mid].compareTo(target) < 0)
                start = mid;
            else
                end = mid - 1;
        }
        return start;
    }


    /**  扩展派生:
     * 与lower_ciel的区别,如果不存在就返回比其小的最大值的最大索引
     * 1,1,3,3,5,5,找4返回第二个三,找5返回最大的5
     */
    public static <E extends Comparable<E>> int lower_floor(E arr[], E target) {
        int lower = lower(arr, target);
        if (lower + 1 <= arr.length - 1 && arr[lower + 1].compareTo(target) == 0)
            return lower + 1;
        return lower;
    }

    //2、存在目标元素返回下标最大的那个--->相同元素最大下标
    public static <E extends Comparable<E>> int upper_floor(E arr[], E target) {
        int start = -1, end = arr.length - 1;
        while (start < end) {
            int mid = (end + start + 1) / 2;
            if (arr[mid].compareTo(target) <= 0) {
                start = mid;
            } else {
                end = mid - 1;
            }
        }
        return start;
    }

 上述两种方法的总结:

如果要找比其大的最小值,就让右边界为arr.length。

要找比其小的最大值,就让左边界起始=-1,并且为了保证mid的有效性让mid=(l+r+1)/2;


可以将上述两种方法总结成一个模板:

1、l和r永远是查询空间。

2、while循环是否取等就看l=r时这个数组是否是空数组。

3、if中的条件都是保证满足函数目的的。(例:upper是要找比目标元素大的第一个元素,它不需要返回目标元素只需要比目标元素大的,所以if条件中不需要等号,low_ciel最终会返回目标元素或比目标元素大的第一个元素,这里需要返回目标元素,所以就需要有等号。)

4、最后的返回值,返回l和r都行,通过代码可以验证最后循环结束的条件总是l=r。


leetCode练习:

1、875题目:

        珂珂喜欢吃香蕉。这里有 N 堆香蕉,第 i 堆中有 piles[i] 根香蕉。警卫已经离开了,将在 H 小时后回来。珂珂可以决定她吃香蕉的速度 K (单位:根/小时)。每个小时,她将会选择一堆香蕉,从中吃掉 K 根。如果这堆香蕉少于 K 根,她将吃掉这堆的所有香蕉,然后这一小时内不会再吃更多的香蕉。 珂珂喜欢慢慢吃,但仍然想在警卫回来前吃掉所有的香蕉。返回她可以在 H 小时内吃掉所有香蕉的最小速度 K(K 为整数)。

/**对于二分查找问题,首先就是要先确定左右边界是什么*/
    //三、875找的就是满足条件的最大值
    /**二刷心得:
     * 1、start和end不是固定的就必须是0-arr.length等,这是问题的边界。
     *      本题中是吃的速度,最小为1,最大为数组中最大的元素。
     *  2、分析换值问题时根据if条件。本题是mid越大得到的值越小,所以最终变化区间时与之前不同。
     * */
    public int minEatingSpeed(int[] arr, int h) {
        //边界:每小时只能吃一堆,如果速度=max(arr)一共要arr.length小时,是时间最短。speed=1时,时间最长。
        int start = 1, end = Arrays.stream(arr).max().getAsInt();
        /**本题的循环不变量就是[1,max(arr)]*/
        while (start < end) {
            int mid = (start + end) / 2;

            if (eatTime(arr, mid) <= h) {
                //此时其可能就已经是解了,于是不能把这个mid对应的值删掉
                //mid越小,时间越长,于是要让mid减小,end就得变小
                end = mid;
            } else {
                //每小时吃的太少了,得多吃点,于是要在右边找
                start = mid + 1;
            }
        }
        return start;
    }
    //  每小时吃k个,res小时吃完。k越大res越小
    private int eatTime(int[] arr, int k) {
        int res = 0;
        for (int one : arr)
            res += one / k + (one % k != 0 ? 1 : 0);
        return res;
    }

2、1011题:

        传送带上的包裹必须在 days 天内从一个港口运送到另一个港口。传送带上的第 i 个包裹的重量为 weights[i]。每一天,我们都会按给出重量(weights)的顺序往传送带上装载包裹。我们装载的重量不会超过船的最大运载重量。返回能在 days 天内将传送带上的所有包裹送达的船的最低运载能力。

    //四、1011
    /**二刷经验,
     * 1、最小边界:应该是整个数组中元素最大的那个,因为如果最小值小于这个值,最大重量的这个货物将永远不能运走
     * 2、每次day++时,sum不能清零而是应该等于此次的货物重量,否则就会少算一天的重量
     * 3、*/
    public static int shipWithinDays(int[] arr, int days) {
        int l = Arrays.stream(arr).max().getAsInt(), r = Arrays.stream(arr).sum();
        while (l < r) {
            int mid = (l + r) / 2;
            if (sum_day(arr, mid) <= days)
                r = mid;
            else
                l = mid + 1;
        }
        return l;
    }

    //mid越大day越少
    private static int sum_day(int[] arr, int mid) {
        int sum_day = 0;
        int sum = 0;
        for (int one : arr) {
            if (sum + one <= mid)
                sum += one;
            else {
                //**此处不是sum=0,判断是用当前的重量+one
                sum = one;
                sum_day++;
            }
        }
        //最后还剩下货物,所以还要将最后一天的加上
        sum_day++;
        return sum_day;
    }

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java范例开发大全(全书源程序),目录如下: 第1篇  Java编程基础 第1章  Java开发环境的搭建(教学视频:9分钟) 2 1.1  理解Java 2 1.2  搭建Java所需环境 3 1.2.1  下载JDK 3 1.2.2  安装JDK 4 1.2.3  配置环境 5 1.2.4  测试JDK配置是否成功 7 实例1  开发第一个Java程序 7 第2章  Java基础类型与运算符 实例2  自动提升 9 实例3  自动转换 10 实例4  常用基础类型之强制转换 11 2.2  运算符 12 实例5  算术运算符 12 实例6  关系运算符 13 实例7  逻辑运算符 14 实例8  位运算符 15 实例9  移位运算符 16 实例10  转型运算符 17 2.3  其他形式 18 实例11  常量与变量 18 实例12  各种进制的转换 19 实例13  Java中的进制与移位运算符 22 第3章  条件控制语句(教学视频:75分钟) 26 3.1  if控制语句 26 实例14  判断输入的年份是否为闰年 26 实例15  抽奖活动 27 3.2  for语句 28 实例16  小九九乘表 28 实例17  如何列出素数 29 实例18  Java中的递归 31 实例19  男生女生各多少人 32 实例20  求水仙花数 34 实例21  求任意一个正数的阶乘 35 实例22  求n的n次方 35 实例23  利用for循环输出几何图形 36 实例24  杨辉三角 38 3.3  while语句 39 实例25  求1到100之间的和 39 实例26  存上100元需要多少天 40 实例27  输出100之间的所有偶数 41 实例28  如何判断回文数字 42 3.4  do…while语句 43 实例29  输出100之间的所有奇数 44 实例30  求最大的随机数 44 3.5  switch语句 45 实例31  判断字母分类 46 实例32  优良及差 47 实例33  打印任意一年日历 48 实例34  一年四季的划分 51 第2篇  Java数据处理 第4章  异常处理(教学视频:62分钟) 54 4.1  编译时异常 54 实例35  除0发生的算术异常(ArithmeticException) 54 实例36  数组下标越界异常(ArrayIndexOutOfBoundsException) 55 实例37  数组元素类型不匹配异常(ArrayStoreException) 56 实例38  强制类型转换异常(ClassCastException) 56 实例39  索引越界异常(IndexOutOfBoundsException) 57 实例40  空指针异常(NullPointerException) 58 实例41  数字格式转换异常(NumberFornatException) 59 实例42  字符串索引越界异常(StringIndexOutBounds) 60 实例43  操作错误(UnsupportedOperationException) 60 4.2  运行时异常 61 实例44  找不到指定类时发生的异常(ClassNotFoundException) 62 实例45  请求的方不存在(NoSuchMethodException) 63 4.3  try…catch捕获异常 65 实例46  try…catch捕获异常的实例 66 实例47  try…catch…finally捕获异常的实例 67 实例48  try…catch嵌套捕获异常的实例 68 4.4  throws声明异常 69 实例49  throws声明异常实例一 69 实例50  throws声明异常实例二 70 4.5  throw抛出异常 72 实例51  throw抛出异常实例一 72 实例52  throw抛出异常实例二 73 4.6  自定义异常 74 实例53  自定义异常实例一 74 实例54  自定义异常实例二 75 第5章  数组(教学视频:98分钟) 78 5.1  一维数组 78 实例55  一维数组的创建与使用 78 实例56  按相反的顺序输出 79 实例57  奇偶分组 80 实例58  找宝 81 实例59  寻找最小数 82 实例60  我的位置在哪里 83 实例61  复制数组 85 实例62  插入新元素 86 实例63  数组的合并 87 实例64  去除重复元素 88 实例65  数组求和计算 90 实例66  求最大值、最小值和平均值 91 5.2  二维数组 92 实例67  二维数组的创建与使用 92 实例68  矩阵转置 93 实例69  奇数阶幻

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值