你真的懂二分法吗?

news2024/11/28 2:39:38

二分法

二分法非常让我们头痛,不论对于初学者,还是对于有一定编程经验的人来讲,我们都会以为这个思想很简单,而不去在意,可是在实际运用中我们在处理边界条件的时候,往往会要不写出了死循环,要不就会发生数组越界,或者说不知道最终搜索结束的时候结果究竟在哪里为什么有的时候是 left = mid+1 有的时候是 left =mid ,在写的时候尽管很小心考虑了边界情况可是还是会出现错误,那么下面的介绍会让你豁然开朗,作者本人也是经过了很多人写的二分版本,下面会给3种二分版本,一种比较好理解,作为我们日常使用,剩余两种用于我们的知识拓展

前题引入 : 在高中我们学过如何进行找到快速找到线路断的点,在一个有一个断电的节点在一个很长的电线上
我们如何快速的找到这个点在什么地方,我们在任意一端通电,去量中点是否有电如果有电位的话就是另一半出了故障点,
接下来继续在有故障的半段接着我们刚才的操作后,有故障的区间会越来越小,在经过log2N次后我们就可以找到故障的点。

我们在一个有序数组查找某一个固定的值,或者说具有二段性(可以根据一个值来将整个数组或者数组的局部来分成两半),而题目中还要求我们写时间复杂度为O(log n)级别的算法,我们第一考虑必然是二分法。

题目链接

704.二分查找

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

74. 搜索二维矩阵

153. 寻找旋转排序数组中的最小值

剑指 Offer 11. 旋转数组的最小数字

33. 搜索旋转排序数组

162. 寻找峰值

题目由容易到难,由普通的二分查找,到特殊的二分查找,到二分查找的变式,可以按着顺序做的,注意第4个题和第五个题不一样,二分法的二段性可以在这两个题中显现出来,不要看着像就不去做了哦,要仔细分析他为什么错,当然我们可以根据他报错的测试用例来进行筛选写出来,但是如果我们不知道原理的话下次还是很难写出来的。

二分法第一种模板(十分推荐)

下面来给出代码,然后在解释原因,这边只是举了一个升序的例子,下面对于具体问题还是得来具体分析,建议上来不要直接看代码,先看原因,如果比较熟练可以直接看代码。


// 假设数组长度为 N,长度题目会给出,这边用一个宏写代码时不会报错看着舒服一点。
// 数组长度为N,数组的有效下标 0~N-1
#define N 100 
int find(int *arr,int target)
{
    int left = -1; //左边界 
    int right = N ; //右边界
    while(left + 1 != right ) 
    {
        // 防止 left + right 会超过 int 所能表示的范围 
        // 化简后和 ( left + right ) / 2 没有区别
        int mid = left + ( right - left ) / 2;
        if(arr[mid] >= target )
        {
            right = target ;
		}else
        {
            left = target;
		}
	}
    // 返回-1表示target不在该数组中,这里判断防止我们接下来返回的时候发生越界,
    // 我们if判断的时候等于是包在右边界的,我们应该在右边界返回结果,但是当target的值大于数组的最大值的时候
    // left = N-1,right = N ,在我们回收结果的时候会发生越界 
    if(right == N)
    {
        return -1;
	}
    return arr[right]==target ? right : 1;    
}

会让我们纠结的是下面几个点

left = ?
right = ?
while(?)
{
    int mid = left + ( right - left ) / 2;
    if(?)
    {
        
	}else
    {
        
    }
}
// 过滤操作怎么写

return ?;

这些问号的所在地经常是我们会让程序出现错误的地方

为什么要这么写

​ 一个bilibili up主的视频教学

如果有侵权联系我马上会进行删除

我们一定要了解二分法的思想,不然在遇到变式的时候我们还是一头雾水,耐心看完

​ 边界思想,gif可能有点慢。

gif

​ 这里我们把二分查找的过程想象成边界的扩充,我们给他一个条件,他把满足条件的边界在左边,不满足条件的在右边,那么我们在开始的时候因为我们不知道第一个值或者最后一个值是不是满足我们的条件,我们没法把边界直接设置到left = 0right = N-1

​ 以上面有序升序数组找target升序数组中有没有重复值无所谓,如果找到我们返回他的下标,如果找不到我们返回-1。如果我们把left左边理解为是<= target的,右边界是 >target的注意不能在等于了(但是如果想要这里要等于前面就不能要了,下面会详细解释接着看),如果数组中的所有的值都小于arr[0],那么右边界在循环结束后应该是right == 0的才满足我们边界的定义,而left不能等于0,因为0位置也是大于我们的target的值的,那么如果所有的值都大于我们的arr[N-1]的时候,按照我们边界的定义循环结束后left == N-1,因为这个时候arr[N-1]不满足我们的右边界的定义,所以一开始的时候我们能让right =N-1;

​ 所以我们 left right应该是下面的写法

left = -1;
right = N;

如果按照边界的思想,那么到最后的时候左边界应该和右边界相邻,所以我们循环的条件为

while(left + 1 != left)

如果我们按照边界思想,那么我们每次在与arr[mid]比较的时候,我们可以把等于的放到左边中间的值已经比较所以我们把这个条件放到左边界,或者直接放到右边界,不过接收的值需要根据我们的条件而定

// 第一种写法
// 显然左边界的值都小于等于 target,而数组又是有序的
// 那么如果该数组中有target左边界的值就等于target,如果没有左边界的值就会直接等于target
// 所以我们在循环结束对arr[left]的值进行判断就可以了
if(arr[left] <= target)
{
    left = mid;
}else
{
    right = mid;
}
// 第二种写法思路和第一种一样不过写法不一样
if(arr[left] >= target)
{
    right = mid;
}else
{
    left = mid;
}

在循环结束后,我们要考虑极端情况,就是目标值比数组中所有的都大,或者比数组中所有的值都小这种情况,因为我们定义边界的时候,我们的边界初始化的值是一个超出数组边界的一个边界值,不进行判断的话有可能会发生错误

  1. 检查索引是否越界
  2. 检查我们的边界值是否等于我们的目标值
// 如果我们是这一种写法的话
// target的值小于我们数组中的每一个值的的情况下
// 我们循环结束后 left = -1 right =0
while(left +1 !=right)
{
    if(arr[left] <= target)
    {
        left = mid;
    }else
    {
        right = mid;
    }
}

//循环结束后进行判断
//1.
if(left == -1)
{
    return -1;
}
//2.
if(arr[left] == target)
{
    return left;
}else
{
    return -1;
}
// 如果target大于我们的数组中的每一个值
// 我们循环结束后 left = N-1 也就是数组的最后一个元素
// right = N
while(left + 1 != right )
{
    if(arr[left] >= target)
    {
        right = mid;
    }else
    {
        left = mid;
    }
}

// 循环结束后进行判断 
// 1.
if(right == N)
{
    return -1;
}
//2.
if(arr[right] == target)
{
    return right
}
else
{
    return -1;
}

二分法的其他两种模板(拓展知识面)

代码随想录中的写法

假设 N是数组的长度

int left = 0;
int right = N-1;
// [0,N]
while(left <= right)
{
    int mid = left + ( right - left ) / 2;
    if(arr[mid] > target)
    {
        right = mid -1;
    }else if(arr[mid] < target)
    {
        left = mid + 1;
	}
    else
    {
        // 等于的情况
        return mid;
	}
}
// 循环结束后 left 一定是在 right 的后一个位置的
// left == right 那么什么情况会导致 right 和 left 移动呢
// arr[left] > targht arr[right] <target
// 返回主要靠mid 这种方法处理的情况比较单一
int left = 0;
int right = N;
// [0,N)
while(left < right)
{
    int mid = left + ( right - left ) / 2;
    if(arr[mid] > target)
    {
        right = mid -1 ;
    }else if(arr[mid] < target)
    {
        left = mid ;
	}
    else
    {
        // 等于的情况
        return mid;
	}
}

```c++

// [0,N)
while(left < right)
{
    int mid = left + ( right - left ) / 2;
    if(arr[mid] > target)
    {
        right = mid -1 ;
    }else if(arr[mid] < target)
    {
        left = mid ;
	}
    else
    {
        // 等于的情况
        return mid;
	}
}

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

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

相关文章

NVM安装与配置教程

一、NVM简介 在项目开发过程中&#xff0c;使用到vue框架技术&#xff0c;需要安装node下载项目依赖&#xff0c;但经常会遇到node版本不匹配而导致无法正常下载&#xff0c;重新安装node却又很麻烦。为解决以上问题&#xff0c;nvm&#xff1a;一款node的版本管理工具&#xf…

Mysql 索引(一)—— 主键索引的底层原理

索引的作用是提升Mysql的检索速度。 如果没有索引&#xff1a;若我们要在几百万条记录中找出一个名为“张三”的人&#xff0c;这个时候我们只能逐条遍历记录&#xff0c;直至找到这个人&#xff1b;如果有索引&#xff1a;假设名为“张三”的人所在记录的索引为 999&#xff…

【构建工具】webpack5 的性能优化和核心流程

文章目录webpack5 构建性能的极致优化一、使用最新版webpack二、使用 lazyCompilation三、约束 Loader 执行范围四、使用 noParse/externals 跳过文件编译五、开发模式禁用产物优化六、最小化 watch 监控范围七、跳过 TS 类型检查八、优化 ESLint 性能九、source-map简化十、设…

Linux:基于libevent读写管道代码,改进一下上一篇变成可以接收键盘输入

对上一篇进行改进&#xff0c;变成可以接收键盘输入&#xff0c;然后写入管道&#xff1a; 读端代码&#xff1a; #include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <s…

01-MySQL基础-简介安装navicat使用SQL(DDL、DML、(DCL)、DML)

文章目录MySQL基础1&#xff0c;数据库相关概念1.1 数据库1.2 数据库管理系统1.3 常见的数据库管理系统1.4 SQL2&#xff0c;MySQL2.1~2.4 mysql安装2.5 MySQL数据模型3&#xff0c;SQL概述3.1 SQL简介3.2 通用语法3.3 SQL分类4&#xff0c;DDL:操作数据库4.1 查询4.2 创建数据…

PCL 平面拟合方法 对比

目录 一、最小二乘法 (Least Squares, LS) 二、采样一致性&#xff08;Sample Consensus&#xff09;方法 2.1 pcl::LeastMedianSquares (LMedS) 2.2 pcl::RandomSampleConsensus (RANSAC) 2.3 pcl::MEstimatorSampleConsensus (MSAC) 2.4 pcl::RandomizedRandomSampleCo…

【Tomcat】IDEA编译Tomcat源码-手把手教程

一、环境准备Tomcat不同版本之间有一定的兼容性问题~如下图所示&#xff1a;官网地址&#xff1a;https://tomcat.apache.org/whichversion.html下载tomcat9官网上面的源码&#xff1a;这一篇文章主要是带着大家在自己的IDEA跑起来一个Tomcat。使用的版本是Tomcat9.0.55 和 JDK…

如何写出更加契合浙大MBA项目提面申请资料?

现在已经是2月中旬了&#xff0c;最近看到上海很多院校都已经公布了提前面批次相应时间了&#xff0c;等浙大复试工作结束&#xff0c;马上提前面批次时间也会出来。本人2023浙大提面也拿到了优秀&#xff0c;结合本人经验&#xff0c;今天给大家分享下申请材料该如何撰写&…

【论文阅读】Anti-Adversarially Manipulated Attributions for WSSS

一篇CVPR2021上的论文&#xff0c;用于弱监督分割及半监督分割 论文标题&#xff1a; Anti-Adversarially Manipulated Attributions for Weakly and Semi-Supervised Semantic Segmentation&#xff08;AdvCAM&#xff09; 作者信息&#xff1a; 代码地址&#xff1a; htt…

浅谈volatile关键字

文章目录1.保证内存可见性2.可见性验证3.原子性验证4.原子性问题解决5.禁止指令重排序6.JMM谈谈你的理解6.1.基本概念6.2.JMM同步规定6.2.1.可见性6.2.2.原子性6.2.3.有序性6.3.Volatile针对指令重排做了啥7.你在哪些地方用过Volatile&#xff1f;volatile是Java提供的轻量级的…

【消费战略】解读100个食品品牌丨元气森林 6年百亿的饮品黑马成功之道

元气森林成立于2016年&#xff0c;短短六年时间取得了近百亿营收的奇迹&#xff0c;成为让可口可乐、百事、娃哈哈、农夫山泉等消费巨头都无法忽视的对手。六年的成长堪比行业前辈20多年的积累&#xff0c;从这个角度而言&#xff0c;塔望咨询认为元气森林是成功的&#xff0c;…

电影《蚁人与黄蜂女:量子狂潮》观后感

上周看了电影《蚁人与黄蜂女&#xff1a;量子狂潮》&#xff0c;整体有一种像是打开了一座新世界的大门&#xff0c;探索新知的感觉&#xff0c;也许是电影3D效果或者现场原因&#xff0c;给人展示量子世界的时候&#xff0c;总是看不清楚画面细节&#xff0c;稍微有些模糊&…

一文读懂Linux中的进程、线程

一文读懂Linux中的进程、线程 大家好&#xff0c;我是木荣君&#xff0c;这次我们来聊一聊Linux中进程和线程。进程和线程的概念非常重要&#xff0c;本篇来详细介绍下这两者的概念。我们在实际开发中&#xff0c;经常会听到这两个词&#xff0c;如果我们不了解这些词背后的概念…

深入浅出C++ ——二叉搜索树

文章目录一、二叉搜索树概念二、二叉搜索树操作1. 二叉搜索树的查找2. 二叉搜索树的插入3. 二叉搜索树的删除三、二叉搜索树的实现四、二叉搜索树的性能分析一、二叉搜索树概念 二叉搜索树又称二叉排序树/二次查找树&#xff0c;它是一棵空树或者是每颗子树都具有以下性质的二叉…

Spring Boot 框架 集成 Knife4j(内含源代码)

Spring Boot 框架 集成 Knife4j&#xff08;内含源代码&#xff09; 源代码下载链接地址&#xff1a;https://download.csdn.net/download/weixin_46411355/87480176 目录Spring Boot 框架 集成 Knife4j&#xff08;内含源代码&#xff09;源代码下载链接地址&#xff1a;[htt…

Bmp图片格式介绍

Bmp图片格式介绍 介绍 BMP是英文Bitmap&#xff08;位图&#xff09;的简写&#xff0c;它是Windows操作系统中的标准图像文件格式&#xff0c;能够被多种Windows应用程序所支持。随着Windows操作系统的流行与丰富的Windows应用程序的开发&#xff0c;BMP位图格式理所当然地被…

JetPack板块—Android X解析

Android Jetpack简述 AndroidX 是Android团队用于在Jetpack中开发&#xff0c;测试&#xff0c;打包&#xff0c;发布和版本管理的开源项目。相比于原来的Android Support库,AndroidX 可以称得上是一次重大的升级改进。 和Support库一样&#xff0c;AndroidX与Android 操作系…

投出1000份简历,苦于软件测试没有项目经验,全部石沉大海,辞职5个月,我失业了......

想要找一份高薪的软件测试工作&#xff0c;简历项目必不可少&#xff08;即使是应届生&#xff0c;你也要写上实习项目&#xff09;。所以很多自学的朋友找工作时会碰到一个令人颇感绝望的拦路虎&#xff1a;个人并没有实际的项目工作经验怎么办&#xff1f; 怎么办&#xff1f…

6.1 反馈的基本概念及判断方法

一、反馈的基本概念 1、什么是反馈 反馈也称为 “回授”。在电子电路中&#xff0c;将输出量&#xff08;输出电压或输出电流&#xff09;的一部分或全部通过一定的电路形式作用到输入回路&#xff0c;用来影响其输入量&#xff08;放大电路的输入电压或输入电流&#xff09;…

【软件测试】接口自动化测试你真的会做吗?资深测试工程师的总结......

目录&#xff1a;导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09;前言 目的&#xff1f; 通…