【二分查找】一文带你掌握二分法 (附万能模板)

news2025/4/6 2:51:29

目录

      • 一、简介
      • 二、易错点
      • 三、例子
      • 四、万能模板
      • 五、参考资料

一、简介

哪怕没有学过编程的同学,也许不知道二分法这个名字,但也一定接触过它的核心思想。不了解的同学也没关系,我用一句话就能概括出它的精髓:将一个区间一分为二,每次都舍弃其中的一部分。

二分法能够极大地降低我们在解决问题时的时间复杂度。假如你要在一个单调递增的数组a[n]中寻找一个数target,遍历的话时间复杂度是O(n)。但如果采用二分法,时间复杂度则是O(log n)。这种效率的提高无疑是巨大的!

二、易错点

1、while循环中是写 left < right 还是写 left <= right ?

2、如下图所示,if(nums[mid]>target),右边界更新区间时是写成 right = mid 还是 right = mid-1?

请添加图片描述

这两点是很容易弄混的。要弄清楚上面这两个问题的答案,首先要确定你想写的循环的区间到底是左闭右开还是左闭右闭?(题目没明确要求的话就看你个人选择,都是可以的)


左闭右闭的区间是这样写的:[left, right]

它包含了 left 和 right 这两个数


左闭右闭的区间是这样写的:[left, right)

它包含了 left,但不包含 right 。


假设我选择了左闭右闭,此时需要在数组nums中寻找一个数target,找到的话返回下标,没找到的话返回 -1,伪代码如下:

left=0;
right=nums.size()-1;
while(l <= r){ // 注意这里不是 l < r
	int mid = l + (r - l)/2;
	if(nums[mid] > target) right = mid-1;
	else if(nums[mid] < target) left = mid+1;
	else{
		return mid;
	}	
}

首先,为什么选择左闭右闭的区间,第3行就要写成<=呢?因为写入while循环的判断条件应该与你选择的区间相吻合,而左闭右闭的区间包含了left和right,也就是说可能会出现 left== right的情况。如果写成<,那么就漏掉了left==right 这种情况,所以应该写成<=。

其次,第5行右边界为什么更新为 mid-1 而不是更新为 mid?因为我们已经确定了nums[mid] > target,所以mid一定不是我们要找的值,因此在下一轮搜索中,不应再包含mid这个数。而我们选择的是左闭右闭区间,它包含了左右边界,每次搜索时都会包含左右边界。所以为了使已经被排除的mid不被再次搜索,右边界应该更新为mid-1。


假设我选择了左闭右开,此时需要在数组nums中寻找一个数target,找到的话返回下标,没找到的话返回 -1,伪代码如下:

left=0;
right=nums.size();
while(l < r){ // 注意这里不是 l < r
	int mid = l + (r - l)/2;
	if(nums[mid] > target) right = mid;
	else if(nums[mid] < target) left = mid+1;
	else{
		return mid;
	}	
}
return -1;

左闭右开代表包含左边界,不包含右边界。右边界一定比左边界大,不存在 right == left 的情况。所以第3行只能写成<。

而第5行之所以写成right=mid,是因为下一次搜索中本就不会包含right,我们也确定了right不是要找的值,所以直接写成right=mid就可以了。

此时,注意第2行,因为左闭右开不包含右边界,所以右边界要设置为nums.size(),这样才能把nums.size()-1包含在搜索区间里。

三、例子

如果你已经理解了上面的内容,可以尝试做下面这道题:

在这里插入图片描述

左闭右闭:

class Solution {
public:
    int search(vector<int>& nums, int target) {
         int l = 0, r =  nums.size() - 1; 
        while(l <= r){ 
            int m = l + (r - l)/2;
            if(nums[m] > target)r = --m;
            else if(nums[m] < target) l = ++m;
            else{
                return m;
            }
        }
    return -1; 
    }
};

左闭右开:

class Solution {
public:
    int search(vector<int>& nums, int target) {
         int l = 0, r =  nums.size(); 
        while(l < r){ 
            int m = l + (r - l)/2;
            if(nums[m] > target)r = m;
            else if(nums[m] < target) l = ++m;
            else{
                return m;
            }
        }
    return -1; 
    }
};

四、万能模板

尽管理解了上面的内容,但很多时候解题还是会遇到困难。

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

你会发现上面的两个代码很难应用到这种题,所以我找到了一个万能模板,跟大家分享一下:

class Solution {
    /**
     * 如果返回值等于 nums.size(),代表 nums 中不存在 满足 nums[i] >= target 的 i,也就是所有元素都小于 target。
     * 否则返回满足 nums[i] >= target 的最小 i(最左 i)。也就是说,如果有多个连续的 target,会返回最靠左的那个的下标。
     * nums为单调不减数组
     * target为边界值  
     **/
    public: int binarySearch(vector<int>& nums, int target) {
        int l = 0, r = nums.size()- 1; // 二分查找的左右初始边界
        while(l <= r){ // 注意这里不是 l < r
            int m = l + (r - l)/2;
            if(nums[m] >= target)r = --m;
            else l = ++m;
        }
        return l; // 此时,l 代表arr[i] >= x 的最小 i。
    }
};

如何运用这个模板去解决不同的问题呢?请往下看

1、查找某个数 x 首次出现的位置,如果不存在,返回-1。

如果 binarySearch(arr, x) == nums.size(),代表所有元素都小于x,也就无法查找到 x 首次出现的位置;如果有多个元素等于 x,则 binarySearch(arr, x) 代表 x 首次出现的位置的下标;如果binarySearch(arr, x) != x,则不存在某个数等于 x,binarySearch(arr, x) 代表最靠左的大于 x 的数。

2、查找某个数 x 最后出现的位置,如果不存在,返回-1。

将该问题转换为用 int ret = binarySearch(arr, x+1) - 1 来解决。 如果ret < 0,返回 -1;否则,如果nums[ret] == x,ret 就是答案;否则,返回 -1。

3、查找某个数 x 首次出现的位置,如果不存在 x,则求出适合插入 x 的位置。

binarySearch(arr, x)就是。

4、查找小于x的最后一个数。

转换为用binarySearch(arr, x) - 1来解决,注意判断存在性。

5、查找小于x的第一个数。

这是个伪问题。

6、查找大于x的第一个的数。

转换为用binarySearch(arr, x + 1)来解决,注意判断存在性。

7、查找大于x的最后一个的数。

这是个伪问题。

举例:

35. 搜索插入位置

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

请必须使用时间复杂度为 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 <= 1 0 4 10^4 104

  • - 1 0 4 10^4 104 <= nums[i] <= 1 0 4 10^4 104

  • nums 为 无重复元素升序 排列数组

  • - 1 0 4 10^4 104 <= target <= 1 0 4 10^4 104

class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        int l = 0, r =  nums.size() - 1; // 二分查找的左右初始边界
        while(l <= r){ // 注意这里不是 l < r
        int m = l + (r - l)/2;
        if(nums[m] >= target)r = --m;
        else l = ++m;
    }
    return l; // 此时,l 代表arr[i] >= tarhget的最小 i。
    }
};

五、参考资料

1、二分查找万能模板

2、【二分查找】详细图解


📝我的个人主页
🎁欢迎各位→点赞👍 + 收藏⭐️ + 留言📝​
💬总结:希望你看完之后,能对你有所帮助,不足请指正!共同学习交流 🖊
✉️今天你做别人不想做的事,明天你就能做别人做不到的事♐


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

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

相关文章

注意力汇聚:Nadaraya-Watson 核回归

Nadaraya-Watson核回归是具有注意力机制的机器学习范例。 Nadaraya-Watson核回归的注意力汇聚是对训练数据中输出的加权平均。从注意力的角度来看&#xff0c;分配给每个值的注意力权重取决于将值所对应的键和查询作为输入的函数。 注意力汇聚可以分为非参数型和带参数型。 …

这5个Linux安全相关的命令,很实用!

1.ss ss更多人认为是“网络”命令&#xff0c;但该命令也可用于安全目的。 比如&#xff1a; ss&#xff1a;列出所有连接ss -a&#xff1a;列出侦听和非侦听端口ss -t&#xff1a;列出 TCP 连接 2.who who命令列出所有登录的人。 Linux 是多用户的&#xff0c;所以这个命…

VBA检查指定应用程序是否已经打开

VBA中提供了CreateObject和GetObject两种方法获得对象实例&#xff0c;二者的区别在于GetObject用于获取已经打开的应用程序对象&#xff0c;但是如果该应用程序并没有打开&#xff0c;那么将产生运行时错误&#xff0c;代码中需要加入额外的错误处理代码。 在任务管理器中可以…

第17讲:Python中元组的概念以及应用

文章目录1.元组的概念2.元组的基本使用2.1.定义一个元组2.2.定义一个空元组2.3.元组的元素是不可变的2.4.当元组中的元素是一个列表时列表中的元素可变2.5.当元组中只定义一个元素时的注意事项3.列表的所有操作同样适用于元组4.就是想修改元组中的某个元素1.元组的概念 Python…

CVE-2022-33980 Apache Commons Configuration 远程命令执行漏洞分析

漏洞描述 7月6日&#xff0c;Apache官方发布安全公告&#xff0c;修复了一个存在于Apache Commons Configuration 组件的远程代码执行漏洞&#xff0c;漏洞编号&#xff1a;CVE-2022-33980&#xff0c;漏洞威胁等级&#xff1a;高危。恶意攻击者通过该漏洞&#xff0c;可在目标…

3. 其他数仓/BI架构解析

文章目录1. 独立数据集市架构2. 辐射状企业信息工厂Inmon架构范式建模维度建模3. 混合辐射状架构与Kimball架构目前&#xff0c;经过长时间的演进&#xff0c;各种数仓架构之间的区别变得越来越小&#xff0c;且不论哪种数仓架构&#xff0c;都会涉及维度建模。下面是几种常见的…

《项目管理精华》读书笔记

《项目管理精华》读后感 《项目管理精华》书中用实例讲解了项目管理的基本流程及一些项目管理技巧&#xff0c;通俗易懂且让人有读下去的欲望。本书按照项目管理的五大过程组的顺序逻辑加上一些作者认为项目管理中比较有价值的内容来介绍的&#xff0c;十大知识领域穿插在这些…

nodejs+vue+elementui在线水果商城购物网站vscode项目

摘 要 1 Abstract 1 1 系统概述 4 1.1 概述 4 1.2课题意义 4 1.3 主要内容 4 2 系统开发环境 5 2.1 Vue技术介绍 5 端技术&#xff1a;nodejsvueelementui 前端&#xff1a;HTML5,CSS3、JavaScript、VUE 系统分为不同的层次&#xff1a;视图层&#…

哪个室内导航地图好用?画地图用什么软件好?

在互联网服务中&#xff0c;所有互联网巨头均斥巨资布局C端的地图服务&#xff0c;喜闻乐见的地图形式极大地方便了人们在日常生活中的查询位置、了解环境、导航等应用需求&#xff0c;因此取得了巨大的成功。但目前这些地图服务数据的颗粒度基本仅到城市级&#xff1b;园区、景…

CVE-2022-32532 认证绕过漏洞分析

前言 结合自身经历&#xff0c;在使用正则表达式去匹配流量特征时&#xff0c;由于正则表达式中元字符“.”不匹配换行符&#xff08;\n、\r&#xff09;导致一直提取不上所需的流量。而如今&#xff0c;之前踩过的坑却出现在了Apache Shiro框架之中… 漏洞描述 6月29日&…

《MySQL实战45讲》——学习笔记24 “Master/Slave主备同步机制“

binlog可以用来归档&#xff0c;也可以用来做主备同步&#xff0c;binlog在MySQL的各种高可用方案上扮演了重要角色&#xff1b;本篇主要介绍MySQL主备&#xff08;M-S结构&#xff09;的基本原理、不同格式binlog的优缺点和设计者的思考、MySQL双主结构和循环复制问题&#xf…

关于 Linux 系统

个人主页:ζ小菜鸡 大家好我是ζ小菜鸡&#xff0c;小伙伴们&#xff0c;让我们一起来学习Linux系统。 如果文章对你有帮助、欢迎关注、点赞、收藏(一键三连) 目录 一、Linux系统的组成 二、Linux 命令行的操作 三、Linux 目录结构 四、文件基本操作 五、用户及文件权限管…

jspssm小型药店药品管理系统

目 录 第一章 概述 1 1.1 研究背景 1 1.2开发意义 1 1.3 研究现状 1 1.4 研究内容 2 1.5论文结构 2 第二章 开发技术介绍 3 2.1 系统开发平台 3 2.2 平台开发相关技术 3 2.2.1 JSP技术介绍 3 2.2.2 Mysql数据库介绍 3 2.2.3 B/S架构 …

excel排名技巧:万能透视表加筛选找出销售冠军

在一组数据中统计单项排名第一的人员名单&#xff0c;如果用函数&#xff0c;我们需要考虑不同项目业绩相等的查找问题&#xff0c;需要考虑同项目业绩相等的查找问题。但如果用数据透视表加筛选&#xff0c;一切就很简单了。最后要么逐条地复制粘贴&#xff0c;要么用技巧合并…

ChatGPT 逆天测试,结局出乎预料

目录一、数学解题能力二、编程能力三、日常生活咨询四、问一些离谱的问题&#xff0c;它有啥反应&#xff1f;五、逆天大测试一、数学解题能力 据说 ChatGPT 会做数学题&#xff0c;给他几个条件不充分的问题&#xff0c;看看他是否真的会思考。 这家伙心理素质真好&#xff…

Uniswap 解析:恒定乘积做市商模型Constant Product Market Maker Model 的Vyper 实作

大纲 一. 前言 二. 恒定乘积做市商模型Constant Product Market Maker Model 1. 计入手续费 2. 程式码结构 3. 演算法核心与实作 4. 段落小结 三. 流动性Liquidity 1. 第一笔流动性注入、决定k值 2. 除了第一笔以外的情况 四. 结语 一. 前言 暨上一篇开始接触了Vyper 后&…

《吐血整理》保姆级系列教程-玩转Fiddler抓包教程(2)-初识Fiddler让你理性认识一下

1.前言 今天的理性认识主要就是讲解和分享Fiddler的一些理论基础知识。其实这部分也没有什么&#xff0c;主要是给小伙伴或者童鞋们讲一些实际工作中的场景&#xff0c;然后隆重推出我们的猪脚&#xff08;主角&#xff09;-Fiddler。 1.1工作场景 做app测试&#xff0c;你是…

SpringMVC的工作原理

SpringMVC的工作原理图&#xff1a; SpringMVC流程 1、 用户发送请求至前端控制器DispatcherServlet。 2、 DispatcherServlet收到请求调用HandlerMapping处理器映射器。 3、 处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找)&#xff0c;生成处理器对象及处…

二叉树29:二叉搜索树中的插入操作

主要是我自己刷题的一些记录过程。如果有错可以指出哦&#xff0c;大家一起进步。 转载代码随想录 原文链接&#xff1a; 代码随想录 leetcode链接&#xff1a;701.二叉搜索树中的插入操作 题目&#xff1a; 给定二叉搜索树&#xff08;BST&#xff09;的根节点 root 和要插入…

苏嵌实训——day19

文章目录一、数据库1.1 在ubuntu中安装数据库1.2 数据库的操作1.2.1 数据库命令的分类1.2.2 常用的系统命令1.2.3 数据中的常用的语句1.3 sqlite数据库中常用api1. sqlite3_open2. int sqlite3_close(sqlite3 * db)3. sqlite3_exec4 sqlite3_get_table5 void sqlite3_free_tabl…