014、二分搜索算法(labuladong)

文章详细介绍了二分搜索算法的基础框架和应用场景,包括基本的数搜索、寻找左侧和右侧边界的二分查找。强调了循环条件、区间更新以及处理数组边界和重复元素的关键点。同时提供了不同变体的代码实现,如左闭右闭和左闭右开区间的选择。
摘要由CSDN通过智能技术生成

二分搜索算法

基于labuladong的算法小抄,网站地址点我视频版地址点我
建议直接看网站的文字版,视频作为补充查阅资料

1、二分查找框架

二分查找:细节决定成败,关键是在于到底要给mid加一还是减一,while里面到底用<=还是<
二分查找框架:

    // 二分查找框架(返回target在arr数组中的下标)
    int binarySearch(int[] arr, int target) {
        int left = 0, right = ...;
        // 循环条件
        while (...){
            int mid = left + (right - left) / 2;
            if (target == arr[mid]) {
                ...
            } else if (target < arr[mid]) {
                right = ...;
            } else if (target > arr[mid]) {
                left = ...;
            }
        }
        return ...;
    }

技巧总结:

  • 不要出现else,而是把所有可能性用else if写清楚
  • … 就是可能出现细节问题的地方
  • 计算mid时要注意溢出的问题
  • 数组必须使单调数组才可以使用该算法

2、寻找一个数

这是一个最基本的二分搜索,即搜索一个数,如果存在,返回其索引,否则返回-1
力扣704题,二分查找
采用左闭右闭区间做:

[704]二分查找

class Solution {
    public int search(int[] nums, int target) {
        int left = 0, right = nums.length - 1;
        // 先行条件判断
        if (target < nums[left] || target > nums[right]) {
            return -1;
        }
        // 在左闭右闭区间进行循环
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (target == nums[mid]) {
                return mid;
            } else if (target < nums[mid]) {
                right = mid - 1;
            } else if (target > nums[mid]) {
                left = mid + 1;
            }
        }
        // 遍历完所有元素都没找到
        return -1;
    }
}
  • 为什么while循环中是<=,而不是<呢?
    • 这个取决于初始化时候的right值,在进行搜索时候每次的区间是左闭右闭还是左闭右开
  • 为什么left=mid+1,right=mid-1呢?
    • 这个取决于每次搜索区间结束后,下一个搜索区间应是什么样子的,如何剔除已经搜索过的mid元素
  • 算法的缺陷
    • 如果数组元素重复,该算法只能找到重复元素中的一个元素,不能向左或者向右线性搜索
    • 需要向左向右线性搜索,就需要左闭右开或者左开右闭区间

3、寻找左侧边界的二分搜索

代码框架如下:

    // 左闭右开区间
    int leftIndex(int[] nums, int target) {
        int left = 0, right = nums.length;
        // 遍历
        while (left < right) {
            int mid = left + (right - left) / 2;

            if (target == nums[mid]) {
                right = mid;
            } else if (target < nums[mid]) {
                right = mid;
            } else if (target > nums[mid]) {
                left = mid + 1;
            }
        }
        return left;
    }
  • 为什么while中是<?
    • 因为是左闭右开区间
  • 为什么没有返回-1的操作?如果nums中不存在target这个值,怎么处理?
    • 在返回的之后判断一下nums[left]是否等于target
    • 还要考虑left是否越界的情况
  • 为什么left=mid+1,right=mid?
    • 因为搜索区间是左闭右开,已经判断过mid
    • 下一步应该去mid的左侧或者右侧区间搜索,即[left,mid)或者是[mid+1,right)
  • 为什么能够搜索到左侧边界?
    • 关键是在于nums[mid]==target这种情况的处理
    • 找到target后不是立即返回,而是更新搜索区间的上界right,不断向左收缩
  • 为什么返回left而不是right?
    • 其实返回left和返回right都是一样的
  • 如果将搜索区间变成左闭右闭,代码如下:
    // 左闭右闭区间
    int leftIndex(int[] nums, int target) {
        int left = 0, right = nums.length - 1;
        // 遍历
        while (left <= right) {
            int mid = left + (right - left) / 2;

            if (target == nums[mid]) {
                right = mid - 1;
            } else if (target < nums[mid]) {
                right = mid - 1;
            } else if (target > nums[mid]) {
                left = mid + 1;
            }
        }
        // 判断是否越界
        if (left == nums.length) {
            return -1;
        }
        // 判断是否找到目标值
        return nums[left] == target ? left : -1;
    }

4、寻找右侧边界的二分查找

左闭右开

    // 左闭右开(寻找右侧边界)
    int rightIndex(int[] nums, int target) {
        int left = 0, right = nums.length;
        // 遍历
        while (left < right) {
            int mid = left + (right - left) / 2;

            if (target == nums[mid]) {
                left = mid + 1;// 收缩左侧边界
            } else if (target > nums[mid]) {
                left = mid + 1;// 去mid的右侧寻找
            } else if (target < nums[mid]) {
                right = mid;// 去mid的左侧寻找
            }
        }
        // 判断是否越界
        if (left - 1 < 0) {
            return -1;
        }
        // 判断是否找到目标值
        return nums[left - 1] == target ? left - 1 : -1;
    }
  • 为什么这个算法可以找到右侧边界?
    • 当nums[mid]==target时,不会立即返回,而是继续向右搜索
  • 为什么最后返回left-1?
    • 关键是在于锁定右侧边界时的这个条件判断

5、逻辑统一

力扣第34题,排序数据中查找元素的第一个和最后一个位置

[34]在排序数组中查找元素的第一个和最后一个位置

class Solution {
    public int[] searchRange(int[] nums, int target) {
        // 初步筛选条件
        if (nums.length == 0 || target < nums[0] || target > nums[nums.length - 1]) {
            return new int[]{-1, -1};
        }
        int leftIndex = leftIndex(nums, target);
        int rightIndex = rightIndex(nums, target);
        return new int[]{leftIndex, rightIndex};
    }

    // 找到开始位置
    int leftIndex(int[] nums, int target) {
        int left = 0, right = nums.length;
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (target == nums[mid]) {
                right = mid;
            } else if (target < nums[mid]) {
                right = mid;
            } else if (target > nums[mid]) {
                left = mid + 1;
            }
        }
        return target == nums[left] ? left : -1;
    }

    // 找到结束位置
    int rightIndex(int[] nums, int target) {
        int left = 0, right = nums.length;
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (target == nums[mid]) {
                left = mid + 1;
            } else if (target < nums[mid]) {
                right = mid;
            } else if (target > nums[mid]) {
                left = mid + 1;
            }
        }
        return target == nums[left - 1] ? left - 1 : -1;
    }
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值