二分查找算法题型总结

🍋二分查找原理

定义

二分查找也称折半查找(Binary Search),它是一种效率较高的查找方法。但是,折半查找要求线性表必须采用顺序存储结构,而且表中元素按关键字有序排列。

经典例题描述
input:[1,2,3,4,5]
key:3
return the index:2
代码实现
public int binarySearch(int[] nums, int key) {
    int l = 0, h = nums.length - 1;
    while (l <= h) {
        int m = l + (h - l) / 2;
        if (nums[m] == key) {
            return m;
        } else if (nums[m] > key) {
            h = m - 1;
        } else {
            l = m + 1;
        }
    }
    return -1;
}
原理分析

时间复杂度:O(logN)

middle计算:

有两种计算中值m的方式:

  1. m=(l+r)/2

    缺点:l+r可能出现加法溢出的情况。

  2. m=l+(r-l)/2

🍋二分查找拓展

经典例题描述

在一个有重复元素的数组中查找 key 的最左位置。

代码实现
public int binarySearch(int[] nums, int key) {
    int l = 0, h = nums.length;
    while (l < h) {
        int m = l + (h - l) / 2;
        if (nums[m] >= key) {
            h = m;
        } else {
            l = m + 1;
        }
    }
    return l;
}
原理分析

与正常版的不同:

  1. h的赋值表达式为h=m
  2. 循环条件为l<h
  3. 最后返回l而不是-1

在 nums[m] >= key 的情况下,可以推导出最左 key 位于 [l, m] 区间中,这是一个闭区间。h 的赋值表达式为 h = m,因为 m 位置也可能是解。

在 h 的赋值表达式为 h = m 的情况下,如果循环条件为 l <= h,那么会出现循环无法退出的情况,因此循环条件只能是 l < h。以下演示了循环条件为 l <= h 时循环无法退出的情况:

nums = {0, 1, 2}, key = 1
l   m   h
0   1   2  nums[m] >= key
0   0   1  nums[m] < key
1   1   1  nums[m] >= key
...

当循环体退出时,不表示没有查找到 key,因此最后返回的结果不应该为 -1。为了验证有没有查找到,需要在调用端判断一下返回位置上的值和 key 是否相等。

🍋1. 求开方

题目描述链接

https://leetcode.cn/problems/sqrtx/description/

题目特点

使用O(logN)的时间复杂度解决该问题,非递减的一维数组。

代码实现

使用二分查找(正常版本)。

public int mySqrt(int x) {
        int l=0,r=x,ans=-1;
        while(l<=r){
            int mid=l+(r-l)/2;
            if ((long)mid*mid<=x){
                ans=mid;
                l=mid+1;
            }else{
                r=mid-1;
            }
        }
        return ans;
    }
时间复杂度和空间复杂度

时间复杂度:O(logN);

空间复杂度:O(1)。

🍋2. 大于给定元素的最小元素

题目描述链接

https://leetcode.cn/problems/find-smallest-letter-greater-than-target/description/

题目特点

使用O(logN)的时间复杂度解决该问题,非递减的一维数组。

代码实现

使用二分查找(正常版本)。

public char nextGreatestLetter(char[] letters, char target) {
    int left=0,right=letters.length-1,n=letters.length;
    while(left<=right){
        int mid=left+(right-left)/2;
        if (letters[mid]<=target){
            left=mid+1;
        }else{
            right=mid-1;
        }
    }
    return left<n?letters[left]:letters[0];
}
时间复杂度和空间复杂度

时间复杂度:O(logN);

空间复杂度:O(1)。

🍋3. 有序数组的 Single Element

题目描述链接

https://leetcode.cn/problems/single-element-in-a-sorted-array/description/

题目特点

使用O(logN)的时间复杂度解决该问题,非递减的一维数组,找出分割点。

代码实现

使用二分查找(拓展版本)。

public int singleNonDuplicate(int[] nums) {
    int l = 0, h = nums.length - 1;
    while (l < h) {
        int m = l + (h - l) / 2;
        if (m % 2 == 1) {
            m--;   // 保证 l/h/m 都在偶数位,使得查找区间大小一直都是奇数
        }
        if (nums[m] == nums[m + 1]) {
            l = m + 2;
        } else {
            h = m;
        }
    }
    return nums[l];
}
时间复杂度和空间复杂度

时间复杂度:O(logN);

空间复杂度:O(1)。

🍋4. 第一个错误的版本题目描述链接

题目描述链接

https://leetcode.cn/problems/first-bad-version/description/

题目特点

使用O(logN)的时间复杂度解决该问题,找出两极分化的分割点。

代码实现

使用二分查找(拓展版本)。

public int firstBadVersion(int n) {
    int left=0,right=n-1;
    while(left<=right){
        int mid=left+(right-left)/2;
        if (isBadVersion(mid)){
            right=mid-1;
        }else{
            left=mid+1;
        }
    }
    return left;
}
时间复杂度和空间复杂度

时间复杂度:O(logN);

空间复杂度:O(1)。

🍋5. 旋转数组的最小数字

题目描述链接

https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array/description/

题目特点

使用O(logN)的时间复杂度解决该问题,非递减的一维数组(类比),找出分割点。

代码实现

使用二分查找(拓展版本)。

public int findMin1(int[] nums) {
    int l = 0, h = nums.length - 1;
    while (l < h) {
        int m = l + (h - l) / 2;
        if (nums[m] <= nums[h]) {
            h = m;
        } else {
            l = m + 1;
        }
    }
    return nums[l];
}
时间复杂度和空间复杂度

时间复杂度:O(logN);

空间复杂度:O(1)。

🍋6. 查找区间

题目描述链接

https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/

题目特点

使用O(logN)的时间复杂度解决该问题,非递减的一维数组,找出分割点。

代码实现

使用二分查找(拓展版本)。

public int[] searchRange1(int[] nums, int target) {
    int first = findFirst(nums, target);
    int last = findFirst(nums, target + 1) - 1;
    if (first == nums.length || nums[first] != target) {
        return new int[]{-1, -1};
    } else {
        return new int[]{first, Math.max(first, last)};
    }
}

private int findFirst(int[] nums, int target) {
    int l = 0, h = nums.length; // 注意 h 的初始值
    while (l < h) {
        int m = l + (h - l) / 2;
        if (nums[m] >= target) {
            h = m;
        } else {
            l = m + 1;
        }
    }
    return l;
}
时间复杂度和空间复杂度

时间复杂度:O(logN);

空间复杂度:O(1)。

🍋题型总结

二分查找使用的条件:

  1. 题目数据特征有类一维数组或一维数组的数据结构;

    解释:类一维数组,例如一个布尔数组,前面一部分都是false,后面一部分都是true,这样的数组默认叫它类一维数组。

  2. 一维数组的排序有非递减/非递增趋势。

注意点:

  1. 依赖数组的下标;
  2. 不适用数据量较少的情况,数据量少,耗费时间长;
  3. 不适用数据量较多的情况,数据量多,电脑存储可能就是离散的,不满足使用二分查找的条件。

优势:把时间复杂度从O(N)降低到O(log2N)。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值