二分查找及其变种

一、概念

二分查找算法(Binary Search Algorithm)是一种在有序数组中查找特定元素的高效搜索方法。

其基本思想是将目标值与数组中间的元素进行比较,如果目标值等于中间元素,则查找成功;如果目标值小于中间元素,则在数组左半部分继续查找;如果目标值大于中间元素,则在数组右半部分继续查找。这个过程将不断重复,直到找到目标值或搜索范围为空为止。

二、实现步骤

2.1 初始化: 确定搜索范围的左右边界,通常用两个指针表示,一个指向数组的起始位置(left,索引0),另一个指向数组的结束位置(right,数组长度减1)。

2.2 循环条件: 当左指针小于等于右指针时,继续查找。

2.3 计算中间索引: 计算当前搜索范围的中间位置 mid,通常使用 (left + right) / 2 来避免整数溢出。

口诀:奇数二分取中间 偶数二分取中间靠左;

2.4 比较中间元素: 比较 array[mid] 与目标值 target:

  • 如果 array[mid] 等于 target,则查找成功,返回 mid。
  • 如果 array[mid] 大于 target,则在左半部分继续查找,更新右指针 right = mid - 1。
  • 如果 array[mid] 小于 target,则在右半部分继续查找,更新左指针 left = mid + 1。

2.5 结束循环: 如果左指针大于右指针,表示搜索范围为空,目标值不在数组中,返回一个表示未找到的值,如 -1。

三、代码实现

java 复制代码
public class BinarySearch {
    public int binarySearch(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;
        
        while (left <= right) {
            int mid = left + (right - left) / 2; // 防止溢出
            if (nums[mid] == target) {
                return mid; // 找到目标值,返回索引
            } else if (nums[mid] < target) {
                left = mid + 1; // 在右半部分查找
            } else {
                right = mid - 1; // 在左半部分查找
            }
        }
        
        return -1; // 未找到目标值,返回-1
    }
}

注意事项:

  1. 如果left 和 right比较大的话,两者之和就有可能会溢出。改进的方法是将 mid 的计算方式写成 left +(right-left )/2。更进一步,如果要将性能优化到极致的话,我们可以将这里的除以 2 操作转化成位运算 left +((right-left )>>1)。因为相比除法运算来说,计算机处理位运算要快得多。

  2. while (left < = right),注意括号内为 left <= right ,而不是 left < right ,如果我们设置条件为 left < right 则当我们执行到最后一步时,则我们的 left 和 right 重叠时,则会跳出循环,返回 -1,区间内不存在该元素,但是不是这样的,我们的 left 和 right 此时指向的就是我们的目标元素 ,但是此时 left = right 跳出循环

  3. left = mid + 1,right = mid - 1 而不是 left = mid 和 right = mid。当我们的target 元素为 16 时,然后我们此时 left = 7 ,right = 8,mid = left + (right - left) = 7 + (8-7) = 7,那如果设置 left = mid 的话,则会进入死循环,mid 值一直为7 。

四、算法变种

4.1

给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。

如果数组中不存在目标值 target,返回 [-1, -1]。

java 复制代码
示例 1:
输入:nums = [5,7,7,8,8,10], target = 8 输出:[3,4]

示例 2:
输入:nums = [5,7,7,8,8,10], target = 6 输出:[-1,-1]

示例 3:
输入:nums = [], target = 0 输出:[-1,-1]

从最原始的二分查找代码中分析, nums[mid] == target 时则返回,nums[mid] < target 时则移动左指针,在右区间进行查找, nums[mid] > target 时则移动右指针,在左区间内进行查找。

那么我们思考一下,如果此时我们的 nums[mid] = target ,但是我们不能确定 mid 是否为该目标数的左边界,所以此时我们不可以返回下标。

此时 mid = 4 ,nums[mid] = 5,但是此时的 mid 指向的并不是第一个 5,所以我们需要继续查找 ,因为我们要找的是数的下边界,所以我们需要在 mid 的值的左区间继续寻找 5 ,那应该怎么做呢?

我们只需在target <= nums[mid] 时,让 right = mid - 1即可,这样我们就可以继续在 mid 的左区间继续找 5 。

解决方案:

将小于和等于合并在一起处理,当 target <= nums[mid] 时,我们都移动右指针,也就是 right = mid -1,还有一个需要注意的就是,我们计算下边界时最后的返回值为 left ,当上图结束循环时,left = 3,right = 2,返回 left 刚好时我们的下边界。

计算上边界时算是和计算上边界时条件相反,

计算下边界时,当 target <= nums[mid] 时,right = mid -1;target > nums[mid] 时,left = mid + 1;

计算上边界时,当 target < nums[mid] 时,right = mid -1; target >= nums[mid] 时 left = mid + 1;刚好和计算下边界时条件相反,返回right。

java 复制代码
public class Solution {
   
    public static int[] searchRange(int[] nums,int target){
        int upper = upperBound(nums, target);
        int low = lowerBound(nums, target);
        //不存在情况
        if (upper < low){
            return new int[]{-1,-1};
        }
        return new int[]{low,upper};
    }

    //计算下边界
    static int lowerBound(int[] nums,int target){
        int left = 0, right = nums.length - 1;
        while (left <= right){
            //计算mid
            int mid = left + ((right - left) >> 1);
            if (target <= nums[mid]){
                //当目标值小于等于nums[mid]时,继续在左区间检索,找到第一个数
                right = mid -1;
            }else if (target > nums[mid]){
                //目标值大于nums[mid]时,则在右区间继续检索,找到第一个等于目标值的数
                left = mid + 1;
            }
        }
        return left;
    }

    //计算上边界
    static int upperBound(int[] nums,int target){
        int left = 0,right = nums.length - 1;
        while (left <= right){
            int mid = left + ((right - left) >> 1);
            if (target >= nums[mid]){
                left = mid + 1;
            }else if (target < nums[mid]){
                right = mid - 1;
            }
        }
        return right;
    }
}
相关推荐
杨和段1 小时前
简介空间复杂度
数据结构
Overboom3 小时前
[数据结构] --- 线性数据结构(数组/链表/栈/队列)
数据结构
T风呤3 小时前
学生管理系统(通过顺序表,获取连续堆区空间实现)
算法
stackY、3 小时前
【Linux】:程序地址空间
linux·算法
心死翼未伤4 小时前
【MySQL基础篇】多表查询
android·数据结构·数据库·mysql·算法
Orion嵌入式随想录5 小时前
算法训练 | 图论Part1 | 98.所有可达路径
算法·深度优先·图论
西西,正在减肥5 小时前
【leetcode52-55图论、56-63回溯】
算法
Beast Cheng5 小时前
07-7.1.1 查找的基本概念
数据结构·笔记·考研·算法·学习方法
DogDaoDao5 小时前
LeetCode 算法:二叉树中的最大路径和 c++
c++·算法·leetcode·二叉树·二叉树路径
望舒_2335 小时前
【算法专题】双指针算法
算法