您的位置:首页 > 理论基础 > 数据结构算法

算法与数据结构(二) -- 二分查找及其变形体

2019-05-31 20:33 281 查看
版权声明:本为为原创文章,转载请附上连接! https://blog.csdn.net/qq_36518248/article/details/90722530 4000

作者:opLW
参考:王争老师的 《数据结构与算法之美》

目录

传统二分查找
变形1:查找第一个等于给定值的元素
变形2:查找最后一个等于给定值的元素
变形3:查找第一个大于等于给定值的元素
变形4:查找第一个大于给定值的元素
变形5:查找最后一个小于等于给定值的元素
变形6:查找最后一个小于给定值的元素
具体题目 LeetCode 875

1.传统二分查找
public static int binarySearch(int[] ary, int target) {
int low = 0;
int high = ary.length - 1;
while (low <= high) { // ==1==
int mid = low + (high - low >> 1); ==2==
if (target == ary[mid]) {
return mid;
} else if (target > ary[mid]) {
low = mid + 1; ==3==
} else {
high = mid - 1; ==3==
}
}
return -1;
}
  • 注意点 1 注意这里是
    low <= high
    而不是
    low < high
  • 2 注意这里要用
    int mid = low + (high - low >> 1)
    而不是
    int mid = (low + high) / 2
    优化点1 使用
    low + (high - low >> 1)
    。当使用
    (low + high) / 2
    时,如果两个数比较大,相加可能会引起溢出;优化点2 使用
    >>1
    来代替
    /2
    ,位运算更加适合计算机。同时注意
    (high - low >> 1)
    要加上括号,因为
    >>
    的优先级低于
    +
  • 3 注意
    low
    high
    区间的变化。
  • 优点
      查找的时间复杂度为O(logn)。这样的时间复杂度,即使在数量庞大的数据中查找也有很高的效率。
  • 局限性
      被查找的数据本身应该是有序的,也就是说我们应该维护被查找数据的有序性。所以当数据存在频繁的插入,删除操作时,二分查找不是很合适。
    • 二分查找依赖于顺序表结构,像数组这样在连续的地址上,可使用下标随机访问的。如果是在链表上查找,那么效率会很低,跟线性查找类似。
    • 因为依赖于顺序表结构,所以需要连续的内存空间。那么当数据量很大时就不适合了,因为可能没有足够大的连续内存空间。

    变形1:查找第一个等于给定值的元素
    public static int BSFirstEqual(int[] ary, int target) {
    int low = 0;
    int high = ary.length - 1;
    while (low <= high) {
    int mid =  low + ((high - low) >> 1);
    if (ary[mid] > target) {
    high = mid - 1;
    } else if (ary[mid] < target) {
    low = mid + 1;
    } else { // ==4==
    if ((mid == 0) || (ary[mid - 1] != target)) return mid;
    else high = mid - 1;
    }
    }
    return -1;
    }
    • ary[mid] > target
      ary[mid] < target
      时,与传统的二分查找相似。
    • 4 唯一的不同之处。因为我们要在所有数值等于给定值的元素中找到第一个,所以当
      ary[mid] == target
      时: 如果
      mid==0
      ,即前面没有数据了,那么这个元素就是等于给定数值的所有元素中的第一个了。
    • 如果
      ary[mid - 1] != target
      ,即前一个数据不等于给定数值了,那么这个元素就是等于给定数值的所有元素中的第一个了。
    • 满足以上两个其中一个,即可返回。如果两个都不符合,那么就要
      high = mid - 1
      继续向左查找。

    变形2:查找最后一个等于给定值的元素
    public static int BSLastEqual(int[] ary, int target) {
    int low = 0;
    int high = ary.length - 1;
    while (low <= high) {
    int mid =  low + ((high - low) >> 1);
    if (ary[mid] > target) {
    high = mid - 1;
    } else if (ary[mid] < target) {
    low = mid + 1;
    } else {
    if ((mid == ary.length - 1) || (ary[mid + 1] != target)) return mid;
    else low = mid + 1;
    }
    }
    return -1;
    }

    变形3:查找第一个大于等于给定值的元素
    public static int BSFirstBigEqual(int[] ary, int target) {
    int low = 0;
    int high = ary.length - 1;
    while (low <= high) {
    int mid =  low + ((high - low) >> 1);
    if (ary[mid] >= target) {
    if ((mid == 0) || (ary[mid - 1] < target)) return mid;
    else high = mid - 1;
    } else if (ary[mid] < target) {
    low = mid + 1;
    }
    }
    return -1;
    }
    • 因为是第一个大于等于给定值的元素,所以我们把大于等于集合到一块,即
      ary[mid] >= target
      。并且在其中做判断,当到
      mid == 0
      或者
      ary[mid - 1] < target
      时,停止寻找返回数据;否则继续向左查找。

    变形4:查找第一个大于给定值的元素
    public static int BSFirstBig(int[] ary, int target) {
    int low = 0;
    int high = ary.length - 1;
    while (low <= high) {
    int mid =  low + ((high - low) >> 1);
    if (ary[mid] > target) {
    if ((mid == 0) || (ary[mid - 1] <= target)) return mid;
    else high = mid - 1;
    } else if (ary[mid] <= target) {
    low = mid + 1;
    }
    }
    return -1;
    }

    变形5:查找最后一个小于等于给定值的元素
    public static int BSLastSmallEqual(int[] ary, int target) {
    int low = 0;
    int high = ary.length - 1;
    while (low <= high) {
    int mid =  low + ((high - low) >> 1);
    if (ary[mid] > target) {
    high = mid - 1;
    } else if (ary[mid] <= target) {
    if ((mid == ary.length - 1) || (ary[mid + 1] > target)) return mid;
    else low = mid + 1;
    }
    }
    return -1;
    }

    变形6:查找最后一个小于给定值的元素
    public static int BSLastSmall(int[] ary, int target) {
    int low = 0;
    int high = ary.length - 1;
    while (low <= high) {
    int mid =  low + ((high - low) >> 1);
    if (ary[mid] >= target) {
    high = mid - 1;
    } else if (ary[mid] < target) {
    if ((mid == ary.length - 1) || (ary[mid + 1] >= target)) return mid;
    else low = mid + 1;
    }
    }
    return -1;
    }

    具体题目 LeetCode 875

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

    public int minEatingSpeed(int[] piles, int H) {
    Arrays.sort(piles); //对piles排序
    int low = 1; // 确定最小值
    //确定最大值。 将piles升序排序之后,piles[piles.length - 1]即为区间的最大值
    int high = piles[piles.length - 1];
    while(low < high) {
    int mid = (high - low) / 2 + low;
    int realHour = 0;
    for(int num : piles) {
    realHour = realHour + num / mid; // 计算吃完当前的num需要花费多少个hour
    if(num % mid != 0) // 对于不能被整除的num应该额外加上一个hour
    realHour ++;
    }
    /*
    如果为true,说明mid右边(包括他本身)都能满足,接下来需要往左扩展,找到最小能满足条件的数值
    */
    if(time <= H) // ==1==
    high = mid; // ==2==
    else
    low = mid + 1; // 本值及其左边的都不能满足案例,所以要向右扩展,需要+1,舍弃本值
    }
    return low;
    }
    • 首先如何判断这道题要用二分查找呢?这个问题不太好说,每个人可能有自己的见解。我就说说我个人的理解:首先题目透露出在符合条件的数据中找最小值,这点很符合二分查找的变体;其次由题意可知,K值最坏的情况下可能要和piles中的最大值一样(即10^9),如果对于每种情况我们都计算它是否符合要求,那这个计算量会很大;最后我们知道如果一个值不符合,那么比他小肯定也不符合,这点很符合二分查找的特点。综上,我们采取二分查找。
    • 解体思路 我们知道二分查找是在有序,有界的数组上查找目标值,那么首先要确定
      low
      high
    • 其次要注意变体的处理。(具体看题目注释)
    • 1 这里我们应该结合实际设置判断条件。对于不符合的按照正常的处理。
    • 2 这里把mid直接包含到下一步的计算中。如果按照前面的变体思路,每次都计算mid - 1是否符合要求。这样是可以的,但是为了整洁,我们直接把它放到下一步的计算中去。
  • 总结 首先主要考察能否捕捉到使用二分查找的信息,其次是二分查找的灵活应用(即能否灵活使用变体)。如果这两点做好了,基本可以解出答案,不要考虑的太过复杂。
  • 总结
    • 其实二分查找主要分为三种情况:
      >
      ,
      ==
      ,
      <
      。总结以上规律,我们只要根据题意在符合的情况中进行进一步的判断即可,对于其他不符合的情况按照传统的处理方式处理。特别地当符合的情况中不包含
      ==
      时,我们将
      ==
      和另外一种不符合要求的情况归为一类处理。
    • 其实掌握大体的思路之后,还要具体题目具体分析。

    万水千山总是情,麻烦手下别留情。
    如若讲得有不妥,文末留言告知我,
    如若觉得还可以,收藏点赞要一起。

    opLW原创七言律诗,转载请注明出处

    内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
    标签: