【算法】二分查找

一、算法概念

  • 二分查找算法(Binary Search)是一种在有序数组中查找指定元素的算法。

二、基本思想

  • 二分查找的基本思想是将目标元素与中间元素进行比较,每次比较能够缩小一半的问题规模,以减少需要比较的次数,从而提高查找的效率。它的核心思想是将问题规模缩小

三、算法步骤

二分查找算法的步骤如下:

  1. 确定查找范围:将起始位置(start)设为0,结束位置(end)设为数组长度减1。
  2. 计算中间位置(mid):通过mid = (start + end) / 2计算中间位置。
  3. 比较目标值:将中间位置的元素与目标值进行比较。
    • 如果中间位置的元素等于目标值,表示找到目标值,返回中间位置。
    • 如果中间位置的元素大于目标值,说明目标值可能在左半部分,将结束位置调整为mid - 1。
    • 如果中间位置的元素小于目标值,说明目标值可能在右半部分,将起始位置调整为mid + 1。
  4. 重复上述步骤2和步骤3,直到找到目标值或者起始位置大于结束位置。
  5. 如果起始位置大于结束位置,则表示目标值不存在于数组中。

通过不断将查找范围缩小一半,二分查找算法可以快速定位目标值的位置。它的时间复杂度为O(logn),相比于线性查找的时间复杂度O(n),具有更高的效率。但前提是数组必须是有序的

四、简单实现示例

题目:

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 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

提示:

1 <= nums.length <= 104
-104 <= nums[i] <= 104
nums 为 无重复元素 的 升序 排列数组
-104 <= target <= 104

解法:

以下是Java解法示例:

public int searchInsert(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 left; // 如果没有找到目标值,返回左指针的值,即目标值应该插入的位置
}

步骤讲解:

  1. 定义左指针left为搜索区间的起始位置,初始化为0。
  2. 定义右指针right为搜索区间的结束位置,初始化为数组长度减1。
  3. 使用二分查找的思想,通过计算中间位置mid,将搜索区间分为两部分。
  4. 如果中间位置的元素等于目标值,直接返回中间位置mid
  5. 如果中间位置的元素小于目标值,则目标值应该在右半部分,将左指针left右移,即left = mid + 1
  6. 如果中间位置的元素大于目标值,则目标值应该在左半部分,将右指针right左移,即right = mid - 1
  7. 循环执行步骤3到步骤6,直到左指针left大于右指针right,表示搜索区间无效,退出循环。
  8. 如果没有找到目标值,返回左指针left的值,即目标值应该插入的位置。

时间复杂度分析:

由于每次比较都将搜索空间减半,算法的时间复杂度为O(logn),其中n为数组的长度。

五、10种应用场景及对应步骤拆解方法

  1. 在有序数组中查找某个特定的元素:

    • 初始化左边界left为0,右边界right为数组长度减1。
    • 当left <= right时,进行循环:
      • 计算中间位置mid = (left + right) / 2。
      • 如果目标值等于中间位置的元素,返回mid。
      • 如果目标值小于中间位置的元素,将右边界right更新为mid - 1。
      • 如果目标值大于中间位置的元素,将左边界left更新为mid + 1。
    • 如果循环结束仍未找到目标值,则返回left作为插入位置。
  2. 判定一个元素是否存在于有序数组中:

    • 初始化左边界left为0,右边界right为数组长度减1。
    • 当left <= right时,进行循环:
      • 计算中间位置mid = (left + right) / 2。
      • 如果目标值等于中间位置的元素,返回true。
      • 如果目标值小于中间位置的元素,将右边界right更新为mid - 1。
      • 如果目标值大于中间位置的元素,将左边界left更新为mid + 1。
    • 循环结束后仍未找到目标值,则返回false。
  3. 寻找有序数组的上界和下界:

    • 初始化左边界left为0,右边界right为数组长度减1。
    • 当left <= right时,进行循环:
      • 计算中间位置mid = (left + right) / 2。
      • 如果目标值小于等于中间位置的元素,将右边界right更新为mid - 1。
      • 如果目标值大于中间位置的元素,将左边界left更新为mid + 1。
    • 循环结束后,返回left作为下界,返回right + 1作为上界。
  4. 在旋转有序数组中查找某个特定的元素:

    • 初始化左边界left为0,右边界right为数组长度减1。
    • 当left <= right时,进行循环:
      • 计算中间位置mid = (left + right) / 2。
      • 如果目标值等于中间位置的元素,返回mid。
      • 如果中间位置的元素大于等于左边界的元素,说明左半部分是有序的:
        • 如果目标值位于左半部分,将右边界right更新为mid - 1。
        • 如果目标值不在左半部分,将左边界left更新为mid + 1。
      • 如果中间位置的元素小于等于右边界的元素,说明右半部分是有序的:
        • 如果目标值位于右半部分,将左边界left更新为mid + 1。
        • 如果目标值不在右半部分,将右边界right更新为mid - 1。
    • 循环结束仍未找到目标值,则返回-1。
  5. 在旋转有序数组中查找最小的元素:

    • 初始化左边界left为0,右边界right为数组长度减1。
    • 当left < right时,进行循环:
      • 计算中间位置mid = (left + right) / 2。
      • 如果中间位置的元素大于右边界的元素,说明最小元素在右半部分:
        • 将左边界left更新为mid + 1。
      • 如果中间位置的元素小于右边界的元素,说明最小元素在左半部分或当前位置:
        • 将右边界right更新为mid。
    • 循环结束后,返回left作为最小元素的索引。
  6. 在一个按照某个规律排列的数组中查找某个特定的元素:

    • 初始化左边界left为0,右边界right为数组长度减1。
    • 当left <= right时,进行循环:
      • 计算中间位置mid = (left + right) / 2。
      • 如果目标值等于中间位置的元素,返回mid。
      • 如果目标值小于中间位置的元素,将右边界right更新为mid - 1。
      • 如果目标值大于中间位置的元素,将左边界left更新为mid + 1。
    • 循环结束后仍未找到目标值,则返回-1。
  7. 在山脉数组中查找某个特定的元素:

    • 初始化左边界left为0,右边界right为数组长度减1。
    • 当left <= right时,进行循环:
      • 计算中间位置mid = (left + right) / 2。
      • 如果目标值等于中间位置的元素,返回mid。
      • 如果中间位置的元素大于左边界且大于右边界,说明处于上坡,则:
        • 如果目标值小于中间位置的元素,将左边界left更新为mid + 1。
        • 如果目标值大于中间位置的元素,将右边界right更新为mid - 1。
      • 如果中间位置的元素小于左边界且小于右边界,说明处于下坡,则:
        • 如果目标值小于中间位置的元素,将右边界right更新为mid - 1。
        • 如果目标值大于中间位置的元素,将左边界left更新为mid + 1。
    • 循环结束仍未找到目标值,则返回-1。
  8. 在有重复元素的有序数组中查找某个特定的元素的第一个出现位置:

    • 初始化左边界left为0,右边界right为数组长度减1。
    • 当left <= right时,进行循环:
      • 计算中间位置mid = (left + right) / 2。
      • 如果目标值小于中间位置的元素,将右边界right更新为mid - 1。
      • 如果目标值大于中间位置的元素,将左边界left更新为mid + 1。
      • 如果目标值等于中间位置的元素,将右边界right更新为mid - 1。
    • 判断left的值是否在数组范围内且等于目标值,如果是,则返回left;否则返回-1。
  9. 在有重复元素的有序数组中查找某个特定的元素的最后一个出现位置:

    • 初始化左边界left为0,右边界right为数组长度减1。
    • 当left <= right时,进行循环:
      • 计算中间位置mid = (left + right) / 2。
      • 如果目标值小于中间位置的元素,将右边界right更新为mid - 1。
      • 如果目标值大于中间位置的元素,将左边界left更新为mid + 1。
      • 如果目标值等于中间位置的元素,将左边界left更新为mid + 1。
    • 判断right的值是否在数组范围内且等于目标值,如果是,则返回right;否则返回-1。
  10. 在有重复元素的有序数组中,查找小于等于目标值的最大元素:

    • 初始化左边界left为0,右边界right为数组长度减1。
    • 当left <= right时,进行循环:
      • 计算中间位置mid = (left + right) / 2。
      • 如果目标值小于中间位置的元素,将右边界right更新为mid - 1。
      • 如果目标值大于等于中间位置的元素,将左边界left更新为mid + 1。
    • 返回right作为最大元素的索引。

六、算法优势及使用限制

优点:

  1. 时间复杂度较低:二分查找的时间复杂度是O(logn),比较高效。
  2. 适用范围广:二分查找适用于有序数组,可以查找某个特定值的位置,也可以查找最接近特定值的位置
  3. 代码简洁:二分查找算法的实现比较简单,代码量较少。

缺点:

  1. 依赖于有序数组:二分查找算法只适用于有序数组,如果数组无序,需要先进行排序操作,增加了额外的时间复杂度。
  2. 无法处理重复元素:二分查找算法只能找到一个特定值的位置,如果有重复元素,无法保证找到的是最左边的、最右边的还是其他位置的元素。
  3. 数据量较小时效率不高:当数据量较小时,二分查找的优势不明显,甚至可能比简单线性查找更慢。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值