数据结构与算法 - 二分查找

news2024/12/24 2:13:02

一、二分查找

二分查找算法也称折半查找,是一种非常高效的工作于有序数组的查找算法。

时间复杂度

  • 最坏情况:O(log n)
  • 最好情况:如果待查找元素恰好在数组中央,只需要循环一次O(1)

空间复杂度

  • 递归->O(log n);迭代->O(1)

1. 基础版

需求:在有序数组a内,查找值target

  • 如果找到返回索引
  • 如果找不到返回-1

算法描述

前提给定一个包含n个元素的有序数组A,满足A{0}≤A{1}≤A{2}≤...≤A{n-1},一个待查值target 
1设置i=0, j=n-1
2如果i>j,结束查找,没找到
3设置m=floor((i+j)/2),m为中间索引,floor是向下取整
4如果target<A{m},设置j=m-1,跳到第2步
5如果target>A{m},设置i=m+1,跳到第2步
6如果A{m}=target,结束查找,找到了

Java实现:

public static int binarySearch(int[] a, int target) {
    int i = 0, j = a.length - 1;
    while (i <= j) {
        int m = (i + j) >>> 1;
        if (target < a[m]) {			// 在左边
            j = m - 1;
        } else if (a[m] < target) {		// 在右边
            i = m + 1;
        } else {
            return m;
        }
    }
    return -1;
}
  • i,j 对应着搜索区间 [0,a.length-1](注意是闭合的区间),i<=j 意味着搜索区间内还有未比较的元素,i, j 指向的元素也可能是比较的目标

    • 思考:如果不加 i==j 行不行?

    • 回答:不行,因为这意味着 i,j 指向的元素会漏过比较

  • m 对应着中间位置,中间位置左边和右边的元素可能不相等(差一个),不会影响结果

  • 如果某次未找到,那么缩小后的区间内不包含 m

2. 改变版

public static int binarySearch(int[] a, int target) {
    int i = 0, j = a.length;
    while (i < j) {
        int m = (i + j) >>> 1;
        if (target < a[m]) {			// 在左边
            j = m;
        } else if (a[m] < target) {		// 在右边
            i = m + 1;
        } else {
            return m;
        }
    }
    return -1;
}
  • i,j 对应着搜索区间 [0,a.length)(注意是左闭右开的区间),i<j 意味着搜索区间内还有未比较的元素,j 指向的一定不是查找目标

    • 思考:为啥这次不加 i==j 的条件了?

    • 回答:这回 j 指向的不是查找目标,如果还加 i==j 条件,就意味着 j 指向的还会再次比较,找不到时,会死循环

  • 如果某次要缩小右边界,那么 j=m,因为此时的 m 已经不是查找目标了

3. 平衡版

public static int binarySearchBalance(int[] a, int target) {
    int i = 0, j = a.length;
    while (1 < j - i) {
        int m = (i + j) >>> 1;
        if (target < a[m]) {
            j = m;
        } else {
            i = m;
        }
    }
    return (a[i] == target) ? i : -1;
}

思想:

  1. 左闭右开的区间,i 指向的可能是目标,而 j 指向的不是目标

  2. 不奢望循环内通过 m 找出目标, 缩小区间直至剩 1 个, 剩下的这个可能就是要找的(通过 i)

    • j - i > 1 的含义是,在范围内待比较的元素个数 > 1

  3. 改变 i 边界时,它指向的可能是目标,因此不能 m+1

  4. 循环内的平均比较次数减少了

  5. 时间复杂度 Θ(log(n))

4. Java版 - 基础版

private static int binarySearch0(long[] a, int fromIndex, int toIndex,
                                     long key) {
    int low = fromIndex;
    int high = toIndex - 1;

    while (low <= high) {
        int mid = (low + high) >>> 1;
        long midVal = a[mid];

        if (midVal < key)
            low = mid + 1;
        else if (midVal > key)
            high = mid - 1;
        else
            return mid; // key found
    }
    return -(low + 1);  // key not found.
}
  • 例如 [1,3,5,6] 要插入 2 那么就是找到一个位置,这个位置左侧元素都比它小

    • 等循环结束,若没找到,low 左侧元素肯定都比 target 小,因此 low 即插入点

  • 插入点取负是为了与找到情况区分

  • -1 是为了把索引 0 位置的插入点与找到的情况进行区分

5. Leftmost

有时我们希望返回的是最左侧的重复元素,如果采用Basic二分查找

  • 对于数组 [1, 2, 3, 4, 4, 5, 6, 7],查找元素4,结果是索引3

  • 对于数组 [1, 2, 4, 4, 4, 5, 6, 7],查找元素4,结果也是索引3,并不是最左侧的元素

public static int binarySearchLeftmost1(int[] a, int target) {
    int i = 0, j = a.length - 1;
    int candidate = -1;
    while (i <= j) {
        int m = (i + j) >>> 1;
        if (target < a[m]) {
            j = m - 1;
        } else if (a[m] < target) {
            i = m + 1;
        } else {
            candidate = m; // 记录候选位置
            j = m - 1;     // 继续向左
        }
    }
    return candidate;
}

6. Rightmost

如果希望返回的是最右侧元素

public static int binarySearchRightmost1(int[] a, int target) {
    int i = 0, j = a.length - 1;
    int candidate = -1;
    while (i <= j) {
        int m = (i + j) >>> 1;
        if (target < a[m]) {
            j = m - 1;
        } else if (a[m] < target) {
            i = m + 1;
        } else {
            candidate = m; // 记录候选位置
            i = m + 1;	   // 继续向右
        }
    }
    return candidate;
}

7. 应用

对于Leftmost与Rightmost,可以返回一个比-1更有用的值

Leftmost改为

public static int binarySearchLeftmost(int[] a, int target) {
    int i = 0, j = a.length - 1;
    while (i <= j) {
        int m = (i + j) >>> 1;
        if (target <= a[m]) {
            j = m - 1;
        } else {
            i = m + 1;
        }
    }
    return i; 
}
  • Leftmost返回值的另一层含义:小于target的元素个数,≥target的最靠左索引
  • 小于等于中间值,都要向左找

Rightmost改为

public static int binarySearchRightmost(int[] a, int target) {
    int i = 0, j = a.length - 1;
    while (i <= j) {
        int m = (i + j) >>> 1;
        if (target < a[m]) {
            j = m - 1;
        } else {
            i = m + 1;
        }
    }
    return i - 1;
}
  • Rightmost返回值的另一层含义:≤target的最靠左索引
  • 大于等于中间值,都要向右找

8. 几个名词

范围查询

  • 查询x<4,0...leftmost(4) - 1
  • 查询x ≤ 4,0...rightmost(4)
  • 查询4 < x,rightmost(4) + 1 ... infty
  • 查询4 ≤ x,leftmost(4) ... infty
  • 查询4 ≤ x ≤ 7,leftmost(4) ... rightmost(7)
  • 查询4 < x < 7,rightmost(4) + 1 ... leftmost(7) - 1

求排名:leftmost(target) + 1

  • target可以不存在,如:leftmost(5) + 1 = 6
  • target也可以存在,如:leftmost(4) + 1 = 3

求前任:leftmost(target) - 1

  • leftmost(3) - 1 = 1,前任a[1] = 2
  • leftmost(4) - 1 = 1,前任a[1] = 2

求后任:rightmost(target) + 1

  • rightmost(5) + 1 = 5,后任a[5] = 7
  • rightmost(4) + 1 = 5,后任a[5] = 7

求最近邻居

  • 前任和后任距离更近者

9. 习题

9.1 二分查找

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

示例 1:

输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4

示例 2:

输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1

提示:

  1. 你可以假设 nums 中的所有元素是不重复的。
  2. n 将在 [1, 10000]之间。
  3. nums 的每个元素都将在 [-9999, 9999]之间。
class Solution {
    public int search(int[] nums, int target) {
        int i = 0, j = nums.length;
        while(i < j) {
            int m = (i + j) >>> 1;
            if(nums[m] < target) {
                i = m + 1;
            } else if(target < nums[m]) {
                j = m;
            } else {
                return m;
            }
        }

        return -1;
    }
}

9.2 搜索插入位置

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

请必须使用时间复杂度为 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 <= 10^4
  • -10^4 <= nums[i] <= 10^4
  • nums 为 无重复元素 的 升序 排列数组
  • -10^4 <= target <= 10^4

解法一:用二分查找基础版代码改写,基础版中,找到返回m,没找到i代表插入点

class Solution {
    public int searchInsert(int[] nums, int target) {
        int i = 0, j = nums.length;
        while(i < j) {
            int m = (i + j) >>> 1;
            if(target < nums[m]) {
                j = m;
            } else if(nums[m] < target) {
                i = m + 1;
            } else {
                return m;
            }
        }

        return i;
    }
}

解法二:用二分查找法平衡板改写,平衡版中

  • 如果 target == a[i] 返回 i 表示找到

  • 如果 target < a[i],例如 target = 2,a[i] = 3,这时就应该在 i 位置插入 2

  • 如果 a[i] < target,例如 a[i] = 3,target = 4,这时就应该在 i+1 位置插入 4

class Solution {
    public int searchInsert(int[] nums, int target) {
        int i = 0, j = nums.length;
        while(1 < j - i) {
            int m = (i + j) >>> 1;
            if(target < nums[m]) {
                j = m;
            } else {
                i = m;
            }
        }
        return (target <= nums[i]) ? i : i + 1;
    }
}

解法三:用leftmost版本解,返回值即为插入位置(并能处理元素重复的情况)

class Solution {
    public int searchInsert(int[] nums, int target) {
        int i = 0, j = nums.length;
        while(i < j) {
            int m = (i + j) >>> 1;
            if(target <= nums[m]) {
                j = m;
            } else {
                i = m + 1;
            }
        }
        return i;
    }
}

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

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

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

你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。

示例 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]

提示:

  • 0 <= nums.length <= 10^5
  • -10^9 <= nums[i] <= 10^9
  • nums 是一个非递减数组
  • -10^9 <= target <= 10^9
class Solution {
    public static int left(int[] a, int target) {
        int i = 0, j = a.length;
        int candidate = -1;
        while(i < j) {
            int m = (i + j) >>> 1;
            if(target < a[m]) {
                j = m;
            } else if(a[m] < target) {
                i = m + 1;
            } else {
                candidate = m;
                j = m;
            }
        }
        return candidate;
    }

    public static int right(int[] a, int target) {
        int i = 0, j = a.length;
        int candidate = -1;
        while(i < j) {
            int m = (i + j) >>> 1;
            if(target < a[m]) {
                j = m;
            } else if(a[m] < target) {
                i = m + 1;
            } else {
                candidate = m;
                i = m + 1;
            }
        }
        return candidate;
    }

    public int[] searchRange(int[] nums, int target) {
        int x = left(nums, target);
        if(x == -1) {
            return new int[] {-1, -1};
        } else {
            return new int[] {x, right(nums, target)};
        }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1960724.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

暑期C++ printf和scanf的平替

有任何不懂的问题可以评论区留言&#xff0c;能力范围内都会一一回答 C中也有专门的输入和输出的方法 首先我们需要一个头文件&#xff0c;也就是#include<iostream> 然后根据我们命名空间的知识可知这个地方如果我们要使用必须先展开 可以全部展开比如using namespa…

K8s大模型算力调度策略的深度解析

随着大数据和人工智能技术的飞速发展&#xff0c;Kubernetes&#xff08;简称K8s&#xff09;作为容器编排的领军者&#xff0c;在支撑大规模模型训练和推理方面扮演着越来越重要的角色。在大模型算力的调度过程中&#xff0c;如何高效、合理地分配和管理资源成为了一个亟待解决…

实验2-5-1 求排列数

本题要求实现一个计算阶乘的简单函数&#xff0c;使得可以利用该函数&#xff0c;根据公式 算出从n个不同元素中取出m个元素&#xff08;0<m≤n&#xff09;的排列数。 函数接口定义&#xff1a; double fact( int n );其中n是用户传入的参数&#xff0c;函数返回n的阶乘。…

数据分析概要【数据分析---偏企业】

各位大佬好 &#xff0c;这里是阿川的博客&#xff0c;祝您变得更强 个人主页&#xff1a;在线OJ的阿川 大佬的支持和鼓励&#xff0c;将是我成长路上最大的动力 阿川水平有限&#xff0c;如有错误&#xff0c;欢迎大佬指正 数据分析概要前 必看 Python 初阶 Python–语言基础…

[数据集][目标检测]易拉罐底部缺陷检测数据集VOC+YOLO格式1122张5类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;1122 标注数量(xml文件个数)&#xff1a;1122 标注数量(txt文件个数)&#xff1a;1122 标注…

企业获客重要途径-大数据获客系统

企业获客的重要途径之一是通过大数据获客系统。这一系统利用大数据技术和分析方法&#xff0c;帮助企业更精准地获取客户&#xff0c;提高市场营销的效率和效果。 所以整理了以下是大数据获客系统作为企业获客重要途径的详细阐述&#xff1a; 一、大数据获客系统的定义与功能…

永磁同步电机谐波抑制算法(8)——基于自适应带宽扩张状态观测器的采样电流偏置误差补偿办法

1.前言 在上一期内容中&#xff0c;已经介绍了采样电流的偏置误差the current measurement offset error /CMOE&#xff08;这个采样电流偏置误差通常认为是直流DC偏置&#xff0c;所以其在dq电流中会造成一次谐波&#xff09;。如果没看过上一期内容&#xff0c;那先需要补一…

SSRF-labs-master靶场

目录 file_get_content.php sql_connect.php download.php dns-spoofing.php dns_rebinding.php 访问链接 http://127.0.0.1/SSRF/# file_get_content.php 在编程语言中&#xff0c;有一些函数可以获取本地保存文件的内容。这些功能可能能够从远程URL以及本地文件 如果没…

C++第二十九弹---C++继承机制深度剖析(上)

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】【C详解】 目录 1.继承的概念及定义 1.1继承的概念 1.2 继承定义 1.2.1定义格式 1.2.2继承关系和访问限定符 1.2.3继承基类成员访问方式的变化 2.基类和派生…

常见的手电筒芯片功能模式选型 单路双路可用

常见的手电筒芯片如下 单双路输出 带充电功能和不带充电功能的 外围结构简单、无需多余的元器件 搜恒森宇电子了解更多相关功能&#xff01; 首页

Spring随笔

Spring随笔 BeanFactory和ApplictionContextbean增强 AutowiredAnnotationBeanPostProcessor工厂增强 BeanFactory和ApplictionContext BeanFactory装载了bean实例&#xff0c;一个容器&#xff0c;提供了对bean的增删改查 ApplictionContext继承了factory&#xff0c;除此之外…

MyBatis基础配置

一、M y B a t i s 配 置 文 件 1.为什么学习MyBatis配置文件 功能&#xff1a;构建SqlSessionFactory的依据。 意义&#xff1a;MyBatis最为核心的内容&#xff0c;对MyBatis的使用影响很大。 注意&#xff1a;配置文件的层次顺序不能颠倒&#xff0c;一旦颠倒会出现异常。 …

NLP与搜广推常见面试问题

1 auc指标 AUC的两种意义 一个是ROC曲线的面积另外一个是统计意义。从统计学角度理解&#xff0c;AUC等于随机挑选一个正样本和负样本时&#xff0c;模型对正样本的预测分数大于负样本的预测分数的概率。下图为搜广推场景下的一个计算auc的例子

如何查找OBS的终端节点(Endpoint)和访问域名

目录 一、参考链接二、终端节点&#xff08;Endpoint&#xff09;三、访问域名 一、参考链接 https://support.huaweicloud.com/productdesc-obs/obs_03_0152.html 二、终端节点&#xff08;Endpoint&#xff09; OBS为每个区域提供一个终端节点&#xff0c;终端节点可以理解…

JVM性能调优全指南:高流量电商系统的最佳实践

1.G1(Garbage-First) 官网: G1 Garbage Collection G1收集器是Java 7中引入的垃圾收集器,用于替代CMS(Concurrent Mark-Sweep)收集器。它主要针对大内存、多核CPU环境下的应用场景,具有以下特点: 分代收集:G1仍然保留了分代的概念,但新生代和老年代不再是物理隔离的,…

37 列表推导式

列表推导式&#xff08;list comprehension) 也成为列表解析式&#xff0c;可以使用非常简洁的方式对列表或其他可迭代对象的元素进行遍历、过滤或再次计算&#xff0c;快速生成满足特定需求的新列表&#xff0c;代码非常简洁&#xff0c;具有很强的可读性&#xff0c;是 pytho…

抖音视频素材网站有哪些?非常好用的5个抖音视频素材库分享

在打造引人入胜的抖音视频时&#xff0c;选择高品质的视频素材至关重要。优选的素材不仅能够显著提升视频的吸引力&#xff0c;还能让你的作品在众多视频中突出重围。对于抖音创作者而言&#xff0c;让我们探索一些备受推崇的视频素材平台&#xff0c;帮助你制作出既专业又引人…

C:操作符介绍-学习笔记

目录 引言&#xff1a; 1、操作符的分类&#xff1a; 2、原码&#xff0c;反码&#xff0c;补码 2.1 介绍 2.2 作用 3、移位操作符&#xff1a;>>、 << 3.1 左移操作符 &#xff1a;<< 3.1.1 正整数移动 3.1.2 负整数移动 3.2 右移操作符&#xff…

Unity GameObject学习笔记

GameObject成员变量 GameObject静态方法 //准备用来克隆的对象//1.直接是场景上的某个对象//2.可以是一个预制体对象public GameObject Myobj; #region 知识点二 GameObject中的静态方法创建自带几何体只要得到了一个GameObject对象 我就可以得到它身上挂载的任何脚本信息GameO…

YotoR(You Only Transform One Representation)

本文介绍了一种名为YotoR&#xff08;You Only Transform One Representation&#xff09;的新型深度学习目标检测模型。该模型将Swin Transformers与YoloR架构相结合。在自然语言处理领域引起革命的Transformer技术&#xff0c;如今同样对计算机视觉产生了深远影响&#xff0c…