算法-二分查找-01

1.什么是二分查找?

        二分查找,即折半查找,与顺序查找相比更加高效。但要求序列本身有序。

        以升序数列为例,比较一个元素与数列中的中间位置的元素的大小,如果比中间位置的元素大,则继续在后半部分的数列中进行二分查找;如果比中间位置的元素小,则在数列的前半部分进行比较;如果相等,则找到了元素的位置。每次比较的数列长度都会是之前数列的一半,直到找到相等元素的位置或者最终没有找到要找的元素。

2.例题

    .2.1 LeetCode704_二分查找

        2.1.1问题描述

给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target  ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。

示例 1:

输入: nums = [1,3,5,6], target = 5
输出: 2

示例 2:

输入: nums = [1,3,5,6], target = 2
输出: 1

示例 3:

输入: nums = [1,3,5,6], target = 7
输出: 4

示例 4:

输入: nums = [1,3,5,6], target = 0
输出: 0

示例 5:

输入: nums = [1], target = 0
输出: 0

        2.1.2问题分析

        使用二分查找的方法,用 left,right 指针控制 mid 指针的位置,若 target 大于 mid 位置的元素,说明 target 在 mid 之后且不含 mid ,故将 left 移动到 mid+1 的位置;反之,若 target 小于 mid 位置的元素,说明 target 在 mid 之前且不含 mid ,故将 right 移动到 mid-1 的位置。反复进行 上述操作并将对应 mid 位置的元素与target比较,直至找到 target 所在位置。

        因为是查找索引,所以查找范围是 0 ~ len-1。

        2.1.3代码实现

class Solution {
    public int search(int[] nums, int target) {
        int len = nums.length;
        int left = 0,right = len-1,mid = -1;
        while(left<right){
            mid = left+(right-left)/2;
            if(nums[mid]==target)   return mid;
            else if(nums[mid]>target)   right= mid-1;
            else if(nums[mid]<target)   left = mid+1;
        }
        if(nums[left]==target)  return left;
        else    return -1;
    }
}

        2.1.4运行结果

    2.2 LeetCode278_第一个错误的版本

        2.2.1问题描述

你是产品经理,目前正在带领一个团队开发新的产品。不幸的是,你的产品的最新版本没有通过质量检测。由于每个版本都是基于之前的版本开发的,所以错误的版本之后的所有版本都是错的。

假设你有 n 个版本 [1, 2, ..., n],你想找出导致之后所有版本出错的第一个错误的版本。

你可以通过调用 bool isBadVersion(version) 接口来判断版本号 version 是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。

 
示例 1:

输入:n = 5, bad = 4
输出:4

解释:
调用 isBadVersion(3) -> false 
调用 isBadVersion(5) -> true 
调用 isBadVersion(4) -> true
所以,4 是第一个错误的版本。

示例 2:

输入:n = 1, bad = 1
输出:1

        2.2.2问题分析

        这是二分查找的 01模型 ,只需要找到第一个1即可。使用 left,right 指针控制 mid 指针的位置,若 mid 位置的元素是 0 ,说明下一次查找范围应该在 mid 之后且不包含 mid ,故将 left 移动到 mid+1 位置;若 mid 位置的元素是 1 ,说明下一次查找范围应该在 mid 之前且包含 mid (可能这个 1 就是第一个 1),故将 right 移动到 mid 位置。反复执行上述操作直到查找范围为1个元素,即 left=right ,该元素就是“第一个1”。

        因为版本号是从1开始,所以查找范围是 1~n。

        2.2.3代码实现

/* The isBadVersion API is defined in the parent class VersionControl.
      boolean isBadVersion(int version); */

public class Solution extends VersionControl {
    public int firstBadVersion(int n) {
        int left = 1,right = n,mid = 0;
        while(left<right){
            mid = left+(right-left)/2;
            if(isBadVersion(mid))   right = mid;
            else    left = mid+1;
        }
        return left;
    }
}

        2.2.4运行结果

    2.3 LeetCode35_搜索插入位置

        2.3.1问题描述

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

请必须使用时间复杂度为 O(log n) 的算法。

示例 1:

输入: nums = [1,3,5,6], target = 5
输出: 2

示例 2:

输入: nums = [1,3,5,6], target = 2
输出: 1

示例 3:

输入: nums = [1,3,5,6], target = 7
输出: 4

示例 4:

输入: nums = [1,3,5,6], target = 0
输出: 0

示例 5:

输入: nums = [1], target = 0
输出: 0

        2.3.2问题分析

        这个问题是 01模型 的变体,要寻找序列中第一个 >=target 的元素的位置。使用 left,right 指针控制 mid 指针的位置,若 mid 位置的元素小于 target ,说明下一次查找范围应该在 mid 之后且不包含 mid ,故将 left 移动到 mid+1 位置;若 mid 位置的元素大于 target  ,说明下一次查找范围应该在 mid 之前且包含 mid (可能这个元素就是第一个大于 target 的元素),故将 right 移动到 mid 位置。反复进行上述操作并将 mid 位置元素与 target 比较。

        因为是要插入,所以可能会在队尾插入,范围是 0~len。

        2.3.3代码实现

class Solution {
    public int searchInsert(int[] nums, int target) {
        int len = nums.length;
        int left = 0,right = len,mid = 0;
        while(left < right){
            mid = left+(right-left)/2;
            if(nums[mid]==target) return mid;
            else if(nums[mid]>target)   right = mid;
            else    left = mid+1;
        }
        return left;
    }
}

        2.3.4运行结果

3.总结

        写二分查找时,要注意

  • 查找范围是多少,left 取多少,right 取多少;
  • 下一次的查找范围是多少,left 和 right 怎样移动,到底是 mid,还是mid+1,mid-1;
  • 跳出循环的判断条件是什么(一般是left<right);
  • mid 的计算方式:最好使用 mid = left+(right-left)/2 ,不要使用mid = (right+left)/2 ,当数据量巨大时,后者会造成溢出。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值