[算法]LeetCode 专题 -- 二分查找专题(Java)

LeetCode 专题 – 二分查找专题

题目

  • 33搜索旋转排序数组
  • 34在排序数组中查找元素的第一个和最后一个位置
  • 35搜索插入位置
  • 69x的平方根
  • 74搜索二维矩阵
  • 153寻找旋转排序数组中的最小值
  • 162寻找峰值
  • 275H指数II
  • 278第一个错误的版本
  • 287寻找重复数

二分查找总结

大白话:

  1. 统一模板,刷题运用

二分查找、堆、二叉树的操作时间复杂度都是O(logn),有时甚至比O(1)的算法还要高效。

二分查找针对的是一个有序的数据集合,查找思想有点类似分治思想。每次都通过跟区间的中间元素对比,将待查找的区间缩小为之前的一半,直到找到要查找的元素,或者区间被缩小为0.

二分查找思想很简单,但是细节需要把握。我也参照过labuladong的二分模板,也看过wangzheng的讲解。虽然labuladong的模板代码结构上更好看,但是wangzheng的模板更不容易出错。

本文就以wangzheng的模板为基础,总结一下基础二分查找及其四个变体的代码模板。

重点

  1. 循环条件都是low <= high
  2. mid的更新分别位mid = high - 1 或者 mid = low + 1

最简单的二分查找

最简单的情况就是有序数组中不存在重复元素,也是各种二分变种的基础

public int bsearch(int[] a, int n, int value) {
    int low = 0;
    int high = n - 1;

    while (low <= high) {
        int mid = (low + high) / 2;
        if (a[mid] == value) {
            return mid;
        } else if (a[mid] < value) {
            low = mid + 1;
        } else {
            high = mid - 1;
        }
    }

    return -1;
}

关键点

  1. 循环条件是 low<=high
  2. mid = low + (high - low) / 2,防止溢出
  3. low = mid + 1, high = mid - 1,防止死循环

局限性:

  1. 二分查找依赖的是顺序表结构,简单点说就是数组
  2. 二分查找针对的是有序数据
  3. 数据量太小不适合二分查找
  4. 数据量太大也不适合二分查找

变体一:查找第一个值等于给定值的元素

public int bsearch(int[], int n, int value){
    int low = 0;
    int high = n - 1;
    while(low <= high){
        int mid = low + ((high - low) >> 1);
        if(a[mid] > value) high = mid - 1;
        else if (a[mid] < value) low = mid + 1;
        else {
            if((mid == 0) || (a[mid - 1] != value)) return mid;
            else high = mid - 1;
        }
    }
    return -1;
}

如果 mid 等于 0,那这个元素已经是数组的第一个元素,那它肯定是我们要找的;如果 mid 不等于 0,但 a[mid] 的前一个元素 a[mid-1] 不等于 value,那也说明 a[mid] 就是我们要找的第一个值等于给定值的元素。

如果经过检查之后发现 a[mid] 前面的一个元素 a[mid-1] 也等于 value,那说明此时的 a[mid] 肯定不是我们要查找的第一个值等于给定值的元素。那我们就更新 high=mid-1,因为要找的元素肯定出现在 [low, mid-1] 之间。

变体二:查找最后一个值等于给定值的元素

public int bsearch(int[], int n, int value){
    int low = 0;
    int high = n - 1;
    while(low <= high){
        int mid = low + ((high - low) >> 1);
        if(a[mid] > value) high = mid - 1;
        else if (a[mid] < value) low = mid + 1;
        else {
            if((mid == n - 1) || (a[mid + 1] != value)) return mid;
            else low = mid + 1;
        }
    }
    return -1;
}

如果 a[mid] 这个元素已经是数组中的最后一个元素了,那它肯定是我们要找的;如果 a[mid] 的后一个元素 a[mid+1] 不等于 value,那也说明 a[mid] 就是我们要找的最后一个值等于给定值的元素。

如果我们经过检查之后,发现 a[mid] 后面的一个元素 a[mid+1] 也等于 value,那说明当前的这个 a[mid] 并不是最后一个值等于给定值的元素。我们就更新 low=mid + 1,因为要找的元素肯定出现在 [mid+1, high] 之间。

变体三:查找第一个大于等于给定值的元素

public int bsearch(int[], int n, int value){
    int low = 0;
    int high = n - 1;
    while(low <= high){
        int mid = low + ((high - low) >> 1);
        if(a[mid] >= value) {
            if((mid == 0) || (a[mid - 1] < value)) return mid;
            else high = mid - 1;
        }
        else {
            low = mid + 1;
        }
    }
    return -1;
}

如果 a[mid] 小于要查找的值 value,那要查找的值肯定在 [mid+1, high] 之间,所以,我们更新 low=mid+1。

对于 a[mid] 大于等于给定值 value 的情况,我们要先看下这个 a[mid] 是不是我们要找的第一个值大于等于给定值的元素。如果 a[mid] 前面已经没有元素,或者前面一个元素小于要查找的值 value,那 a[mid] 就是我们要找的元素。这段逻辑对应的代码是第 7 行。

如果 a[mid-1] 也大于等于要查找的值 value,那说明要查找的元素在 [low, mid-1] 之间,所以,我们将 high 更新为 mid-1。

变体四:查找最后一个小于等于给定值的元素

public int bsearch(int[], int n, int value){
    int low = 0;
    int high = n - 1;
    while(low <= high){
        int mid = low + ((high - low) >> 1);
        if(a[mid] <= value) {
            if((mid == n - 1) || (a[mid + 1] > value)) return mid;
            else low = mid + 1;
        }
        else {
            high = mid - 1;
        }
    }
    return -1;
}

对于 a[mid] 小于等于给定值 value 的情况,我们要先看下这个 a[mid] 是不是我们要找的第一个值大于等于给定值的元素。如果 a[mid] 后面已经没有元素,或者后面一个元素大于要查找的值 value,那 a[mid] 就是我们要找的元素。

如果 a[mid+1] 也小于等于要查找的值 value,那说明要查找的元素在 [mid+1, high] 之间,所以,我们将 low 更新为 mid+1。

如果 a[mid] 大于要查找的值 value,那要查找的值肯定在 [low, mid-1] 之间,所以,我们更新 high=mid-1。

接下进行实战

大雪菜选的LeetCode十道题目。

69,35,34,74,153,33,278,162,287,275
我把这十题全部按照这个模板来,并且ac了,大家可以看代码感受一下。

微信公众号

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值