专题三_二分查找算法_算法详细总结

目录

二分查找

1.⼆分查找(easy)

1)朴素二分查找,就是设mid=(left+right)/2,x=nums[mid],t就是我们要找的值

2)二分查找就是要求保证数组有序的前提下才能进行。

3)细节问题:

总结:

细节:

2.二分查找的进阶:

查找区间左端点:

查找区间右端点:

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

解析:

总结:

总结二分模板:

记忆:

4.搜索插⼊位置(easy)

解析:

5.x的平方根(easy)

解析:

1)暴力:

2)优化:

6.⼭峰数组的峰顶(easy)

解析:

总结:

7.寻找峰值(medium)

解析:

8.搜索旋转排序数组中的最⼩值(medium)

解析:

1)优化:

以D为target:

以A为target:

总结:

9.LCR 点名

解法一:

解法二:

总结一下:


二分查找

二分查找算法,当我们发现一个规律,能在这个规律里面选取一个点之后,能把数组分成两部分,有选择性的选择一部分,进而能在另一部分继续选择,就说明数组有“二段性”。不一定非要数组完全有序,只要能找到一部分有规律,就可以采用二分查找。我们可以找这个数组的二分之一、三分之一、四分之一都行,因为只需要我们能将数组分成两部分就ok。

只要弄清除二分查找算法的进阶,查找"数组"的最左端点 或 查找"数组"的最右端点,对于二分查找的问题一般就迎刃而解了。

那么我们先来看一下朴素的二分查找,后面就来进阶:

1.⼆分查找(easy)

这题就是朴素的二分查找,简直入门级别。了解什么是二分,在什么情况下使用二分。

解析:

1)朴素二分查找,就是设mid=(left+right)/2,x=nums[mid],t就是我们要找的值

1.x<t ,lid-1   ->[left,right];
3.x==t   返回结果

2)二分查找就是要求保证数组有序的前提下才能进行。

3)细节问题:

1.循环结束的条件:left>right,那么while(left<=right)
2.为什么是正确的?
是从暴力解法优化过来的
3.时间复杂度,O(logN)

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int n=nums.size();
        int left=0,right=n-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 -1;
    }
};

总结:

二分查找经典题目。首先最重要的就是判断循环的结束条件,left>right,因为在这之前,left==right的时候,仍然要判断一次nums[left]是否等于target,所以循环条件就是while(left<=right) 然后就是固定的套路

朴素二分查找,就是设mid=(left+right)/2,x=nums[mid],t就是我们要找的值
1.x<t ,left=mid+1   ->[left,right];
2.x>t, right=mid-1   ->[left,right];
3.x==t   返回结果

细节:

为了防止right 和 left 都太大,防止left+right 会存在溢出的风险,所以最好就不要采用这种直接相加的方式。 我们可以先求出这个数组的一半,然后再用这一半长度加上left起始位置,就同样可以得到(left+right)/2 的效果 即:mid=left+(left+right)/2

总结:

二分查找的模板:

     while(left<=right)
        {
            int mid=left+(right-left)/2;
            if(nums[mid]==target) return mid;
            else if(.....) left=mid+1;
            else right=mid-1;
        }

根据题目要求,往里面填入相关的条件,最主要的判断条件就是left<=right;二段性。

2.二分查找的进阶:

面对二分查找,那么数组是必须要有序才能进行吗?难道数组不有序,就不能进行二分查找了嘛?

面对这些问题,就有了二分的进阶,可以说学会二分进阶,简直无敌~

我们先来上一个简单示例:

eg:

要我们查早数组某一重复元素的开始和结尾的下标,任何利用O(logN) 的时间复杂度进行查找。

第一肯定会想到暴力,但时间复杂度是O(N) 太大,那么任何利用二分,就可以完美解决这个问题。

所以就想到利用二分的性质,将数组分两块!!!二段性~

一块 < t ; 一块 >= t 

那么由此就可以看出,我们要利用二分的性质来查找这一块区间的最左端点和最右端点,就能满足条件,因为这个数组满足二段性,那么如何实现呢?请看下面例子:

查找区间左端点:

1.循环区间:
x<t   ->   left=mid+1   [left,right]
x>=t   ->   right=mid   [left,right]

2.循环条件 left<right  
1).如果left==right  就是最终的结果,无需判断
2).如果判断,那么就会进入死循环,因为mid=(left+right)/2,right=mid,就会一直循环

3.求中点操作
1).left+(right-left)/2, 不能用left+(right-left+1)/2,因为这样的话,如果数组元素个数是偶数个,那么mid就等之间右边的一个元素,这时,right就跳不到left的位置,一直在mid这个位置进行死循环

4.为什么要用right=mid,而不是mid-1;因为我们判断的区间是,在大于等于t这个区间,那么就可能存在等于t的情况,如果冒然写right=mid-1,很可能会跳过nums[right]==t,这个值的区间,所以只能让right=mid

5.为什么判断while(left<=right) 就会死循环?
比如left==right==mid,这三个指针,都指向同一个下标,还要进行判断,那么right就一直等于mid 一直仍然进入循环出不来

查找区间右端点:

1.循环区间:

1)x<=t  那么此时x=nums[mid] 落在区间  [小于等于t] 的位置 那么肯定是更新left=mid,,因为我的left不能越过mid,如果left=mid+1,就会越过mid,如果此时的mid就是指向这个区间的最后一个元素,那么left越过后就到了[mid,right] 这个区间,那么这个区间里面就没有最终的结果

2)x>t 同理就是更新right=mid-1,因为这个区间是确定的,是大于target的值,不会存在我们要找的区间的值,就可以越过mid,成为mid-1

2.循环条件:
left<right,因为跟求左端点一样,如果判断等于的话,left,right,mid三个指针在同一个位置,会陷入死循环

3.求中点的方式

1)这次是采用left+(right-left+1)/2,不能采用left+(right-left)/2,因为正好跟求左端点相反,这里求右端点,left==mid,为了防止left一直等于mid陷入死循环,要让mid指向right这一边的中点,所以要让right-left+1

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

经过上面学习到的二分查找数组区间的左右端点,可以直接试试这题,会有很好的效果,是真的简单。

解析:

1)先将数组分块后,对其求mid,然后判断是求左端点还是求右端点

2)确定好后,对于nums[left] < target  ->   left=mid+1 的判断,说明left是可以越过mid的。

对于下面的记忆过程可以确定,上面的mid = left+ (right-left)/2 ,是没有+1的。

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        //查找区间左端点
        int n=nums.size();
        if(n==0) return {-1,-1};
        int left=0,right=n-1;
        vector<int> ret;
        while(left<right)
        {
            int mid=left+(right-left)/2;
            if(nums[mid]<target) left=mid+1;
            else right=mid;
        }
        if(nums[left]==target) ret.push_back(left); else ret.push_back(-1);

        //查找区间右端点
        left=0,right=n-1;
        while(left<right)
        {
            int mid=left+(right-left+1)/2;
            if(nums[mid]>target) right=mid-1;
            else left=mid;
        }
        if(nums[left]==target) ret.push_back(left); else ret.push_back(-1);
        return ret;
    }
};

总结:

1)暴力:
从头开始遍历,那时间复杂度肯定O(N^2) 绝对超时

2)优化,二分查找左右两端点
1.查找左端点
2.查找右端点
模板

总结二分模板:

查找区间左端点:
while(left<right)
        {
            int mid=left+(right-left)/2;
            if(...) left=mid+1;
            else right=mid;
        }

//查找区间右端点
        while(left<right)
        {
            int mid=left+(right-left+1)/2;
            if(...) right=mid-1;
            else left=mid;
        }
 

记忆:

当上面出现+1的时候,下面就是right=mid-1
当上面没有+1 的时候,下面就是left=mid+1

4.搜索插⼊位置(easy)

这题和前面一样,这个数据具有二段性,如果这时的你能够看出数组具有二段性,你就真的已经成功了一大步。将数组分为一块小于target;一块大于等于target;

解析:

还是一样的,简单的二分查找,对于返回target元素的下标,或者插入的下标,只要在>= target这个区间内,找到最左边的元素就ok,然后<target 区间的元素就已经确定,那么left=mid+1

class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        int n=nums.size();
        int left=0,right=n-1;
        if(nums[right]<target) return n;
        if(nums[left]>target) return 0;
        while(left<right)
        {
            int mid=left+(right-left)/2;
            if(nums[mid]<target) left=mid+1;
            else right=mid;
        }
        return right;
    }
};

学会了之后真的是很简单这种题目,利用二分效率还很高。

5.x的平方根(easy)

这一题,我第一次做的时候,确实没能看出来二段性,用的暴力,后来才发现原来这样是一样具有二段性,就比如从1~x ,那么一块,i*i<x ; 另一块是 i*i>=x;数组分两块,这样就可以照样用暴力。 

解析:

1)暴力:

求x的平方根,那么就是找i*i<=x 然后返回i,如果暴力,肯定超时,

2)优化:

可以考虑i~x的值,然后对于i*i 是否小于等于x,求这个区间的最右端点,即利用二分查找求最右端点
考虑到[mid,right] 区间是确定大于x的值,那么right=mid-1,那么上面判断mid就是mid=left+(right-left+1)/2,那么left=mid

class Solution {
public:
    int mySqrt(int x) {
        long long left=0,right=x;
        while(left<right)
        {
            long long mid=left+(right-left+1)/2;
            if(mid*mid>x) right=mid-1;
            else left=mid;
        }
        return left;
    }
};

对于这种二段性数组,掌握了套路简直信手拈来。

6.⼭峰数组的峰顶(easy)

题目意思就是说,要求先增后减的数组的峰值,那么这个二段性也太明显了!!!

解析:

1)那么还是简单的二分查找模板,只需要简单判断left 或 right会不会越过峰值即可。

class Solution {
public:
    int peakIndexInMountainArray(vector<int>& arr) {
        int n=arr.size();
        int left=0,right=n-1;
        while(left<right)
        {
            int mid=left+(right-left)/2;
            if(arr[mid]<arr[mid+1]) left=mid+1;
            else right=mid;
        }
        return left;
    }
};

总结:

面对求峰值在图中可以看出 left 是可以越过mid的,成为mid+1,所以这下right=mid,

mid=left+(right-left)/2,就全都一气呵成了~

7.寻找峰值(medium)

这题简直根上一题大差不差,只是多了几个峰值而已,但实质上,left和right缩小后其实也就只是在一个峰值范围内。

解析:

1)寻找数组中的任意一个峰值即可,在取mid的过程中,left和right就又被缩小到一个峰值,那么根上一题一样只需要判断left和right会不会越过峰值,带模板就能解决

class Solution {
public:
    int findPeakElement(vector<int>& nums) {
        int n=nums.size();
        int left=0,right=n-1;
        while(left<right)
        {
            int mid=left+(right-left)/2;
            if(mid<n-1&&nums[mid]<nums[mid+1]) left=mid+1;
            else right=mid;
        }
        return left;
    }
};

跟上一题一样,就当个练手的,简直easy~

8.搜索旋转排序数组中的最⼩值(medium)

这一题其实二段性超级明显对吧,那么数组分两块,就是根峰值问题不一样的就是数组分两块后全是递增序列,那么我们可以找到里面的规律进行二分。

解析:

1)优化:

这题二分查找有点难度,跟之前的不一样,这个在一个区间内都是单调递增的,所以要单独拿出一个数据当作target,就比如D点,A~B段全是大于D的,C~D全是小于或等于D的。那么求这个数组的最小值,就是求C~D的左端点,那么找最左端点就是二分模板,if(nums[mid]>t) left=mid+1;
            else right=mid;
if(nums[mid]>t) left=mid+1;
            else right=mid;  //nums[mid]<=t,这里是C~D段,为了right不越过最小值点,所以right=mid,left在A~B段进行,所以当mid属于最大值点的时候,left也可以越过变成最小值点 所以left=mid+1

以D为target:

class Solution {
public:
    int findMin(vector<int>& nums) {
        int n=nums.size();
        int left=0,right=n-1;
        int t=nums[n-1];
        while(left<right)
        {
            int mid=left+(right-left)/2;
            if(nums[mid]>t) left=mid+1;
            else right=mid;
        }
        return nums[left];
    }
};

以A为target:

class Solution {
public:
    int findMin(vector<int>& nums) {
        int n=nums.size();
        int left=0,right=n-1;
        int t=nums[0];
        if(n==1) return nums[0];
        while(left<right)
        {
            int mid=left+(right-left)/2;
            if(nums[mid]>=t) left=mid+1;
            else right=mid;
        }
        if(left==n-1&&nums[left]>nums[left-1]) return nums[0];
        return nums[left];
    }
};

总结:

这证明了,二分其实是很随意的,但是只要把内在本质掌握好了,不管以哪个点为标准,都可以很快的找到正确答案。

9.LCR 点名

虽然只是一道简单题,但是要是能想到用二分,那么性能又能提升不少~

解法一:

O(N):

1.哈希表

2.直接遍历找结果

3.位运算

4.数学

解法二:

二分查找 

仍然是寻找二段性,然后判断left是否可以越过mid

class Solution {
public:
    int takeAttendance(vector<int>& records) {
        int n=records.size();
        int left=0,right=n-1;
        while(left<right)
        {
            int mid=left+(right-left)/2;
            if(mid==records[mid]) left=mid+1;
            else right=mid;
        }
        return left==records[left]?left+1:left;
    }
};

总结一下:

我觉得二分最主要就是刚开始学习的查找左右两端点,如果了解这些了,那么对于二分算法就是了解的透透的了。再就是对于有序数组,第一就应该要想到双指针和二分查找,如果数组分块,具有二段性,那不用说了,妥妥的二分!

我觉得对我的进步挺大的,希望对你也有帮助~

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

系统优化工具 | PC Cleaner v9.7.0.3 绿色版

PC Cleaner是一款功能强大的电脑清理和优化工具&#xff0c;旨在通过清理系统垃圾文件、解除恶意软件和优化系统性能来提高计算机的运行效率。该软件提供了多种功能&#xff0c;可以帮助用户维护和提升计算机的整体表现。 PC Cleaner 支持 Windows 7 及以上操作系统&#xff0…

Visual Studio Installer 2022 安装提示正在提取文件 进度条不动 0B每秒

Visual Studio Installer 稍等片刻...正在提取文件 进度条不动 0B每秒 一段时间后提示 循环下载安装文件 无法下载安装文件。请检查Internet 连接&#xff0c;然后重试 打开vs2017 或者vs2019或者vs2022的安装程序(visual studio installer)时&#xff0c;下载进度条不动&…

智能体 vs AI智能体:区别与联系,一文读懂!

​ 在AI技术蓬勃发展的今天&#xff0c;“智能体”&#xff08;Agent&#xff09;和”AI智能体”&#xff08;AI Agent&#xff09;两个概念经常被提及&#xff0c;二者在很多场合下会被混淆&#xff0c;但其实它们有着不同的定义和应用。我觉得很有必要小小科普下两者的定义与…

龙芯+FreeRTOS+LVGL实战笔记(新)——06添加二级按钮

本专栏是笔者另一个专栏《龙芯+RT-Thread+LVGL实战笔记》的姊妹篇,主要的区别在于实时操作系统的不同,章节的安排和任务的推进保持一致,并对源码做了完善与优化,各位可以先到本人主页下去浏览另一专栏的博客列表(目前已撰写36篇,图1所示),再决定是否订阅。此外,也可以…

mysql学习教程,从入门到精通,SQL AND OR 运算符(12)

1、SQL AND & OR 运算符 在本教程中&#xff0c;您将学习如何在子句中使用ASELECT column1_name, column2_name, columnN_nameFROM table_nameWHERE condition1 AND condition2;ND&#xff06;OR运算符&#xff0c;WHERE以根据多个条件过滤记录。 1.1、根据条件选择记录 …

LCSS—最长回文子序列

思路分析 关于”回文串“的问题&#xff0c;是面试中常见的&#xff0c;本文提升难度&#xff0c;讲一讲”最长回文子序列“问题&#xff0c;题目很好理解&#xff1a; 输入一个字符串 s&#xff0c;请找出 s 中的最长回文子序列长度。 比如输入 s"aecda"&#xff0c…

考题:将数组的元素内容反转

考题&#xff1a;把数组的元素内容反转&#xff0c;int[ ] arr {11,22,33,44,55};变成int[ ] arr{55,44,33,22,11} 小伙伴们可以自己先思考再看解析&#xff1a; 方法1&#xff1a; 思想&#xff1a;两杯水交换的思想&#xff08;有一杯装满水的杯子a和一杯装满水的杯子b,想想…

STM32双轮平衡小车(基于STM32F103C8T6HAL库)

STM32双轮平衡小车参考教程 这个项目是跟做以上UP的STM32双轮平衡小车&#xff0c;主要是为了学习电机驱动和PID控制。这篇我就不提供源码了&#xff0c;我也是跟学的&#xff0c;原作者也提供了源码&#xff0c;我记录一下自己的理解。 1 PID原理 1.1 PID简介 1.2 PID演示 …

基于SpringBoot+Vue+MySQL的笔记记录分享网站

系统展示 用户前台界面 管理员后台界面 系统背景 在当今数字化时代&#xff0c;笔记记录与分享已成为学习、工作与生活中不可或缺的一部分。为了满足用户高效整理思绪、便捷分享知识的需求&#xff0c;我们设计了一款基于SpringBoot后端框架、Vue前端框架及MySQL数据库的笔记记…

存储课程学习笔记1_访问scsi磁盘读写测试(struct sg_io_hdr,ioctl,mmap)

创建虚拟机时&#xff0c;可以选择SCSI,STAT,NVME不同类型的磁盘。 0&#xff1a;总结 》了解内核提供的访问scsi的结构和方法 &#xff08;主要是sg_io_hdr_t 结构体和ioctl函数&#xff09;。 》需要读scsi协议文档&#xff0c;了解相关指令&#xff0c;只演示了16字节固定…

Leetcode第414周赛第二题:3281. 范围内整数的最大得分

一&#xff1a;题目&#xff1a; 给你一个整数数组 start 和一个整数 d&#xff0c;代表 n 个区间 [start[i], start[i] d]。 你需要选择 n 个整数&#xff0c;其中第 i 个整数必须属于第 i 个区间。所选整数的 得分 定义为所选整数两两之间的 最小 绝对差。 返回所选整数的…

利他决策的神经机制:脑电功能连接网络分析

摘要 利他主义是一种复杂的亲社会行为&#xff0c;具有多样性的动机和显著的个体差异。研究利他主义的神经机制对于识别行为中的亲社会和反社会倾向的客观标志至关重要。本研究旨在通过网络方法分析基于EEG的功能连接模式来深入探讨利他主义的机制。为了实验性地引发利他决策情…

eclipse配置maven

eclipse配置maven 启动 Eclipse&#xff0c;转到 Window > Preferences 在左侧导航栏中&#xff0c;展开 Maven 节点。 在 User Settings 下&#xff0c;单击 Add。 浏览到 Maven 安装目录中 conf/settings.xml 文件。 在 Global Settings 下&#xff0c;单击 Add。 浏览到…

动态规划算法---04.斐波那契数列模型_解码方法_C++

题目链接&#xff1a;91. 解码方法 - 力扣&#xff08;LeetCode&#xff09;https://leetcode.cn/problems/decode-ways/description/ 一、题目解析 题目&#xff1a; 题目大意&#xff1a;从题目中我们可以知道&#xff0c;解码就是在字符串s中由‘1’到‘26’的字符可以转化…

架构模式:MVC

引言 MVC&#xff0c;即 Model&#xff08;模型&#xff09;-View&#xff08;视图&#xff09;-Controller&#xff08;控制器&#xff09;&#xff0c;是广泛应用于交互式系统中的典型架构模式&#xff0c;尤其在 GUI 和 Web 应用中。 MVC 的概念源自 GOF&#xff08;Gang …

Web安全之GroovyShell讲解:错误与正确示范,安全问题与解决方案

第一章、引言 Groovy 是一门基于 Java 虚拟机&#xff08;JVM&#xff09;的动态语言&#xff0c;而 GroovyShell 是 Groovy 提供的一个灵活强大的脚本执行工具。通过 GroovyShell&#xff0c;开发者可以在运行时动态执行 Groovy 脚本&#xff0c;它的灵活性非常适合那些需要动…

多层建筑能源参数化模型和城市冠层模型的区别

多层建筑能源参数化&#xff08;Multi-layer Building Energy Parameterization, BEP&#xff09;模型和城市冠层模型&#xff08;Urban Canopy Model, UCM&#xff09;都是用于模拟城市环境中能量交换和微气候的数值模型&#xff0c;但它们的侧重点和应用场景有所不同。以下是…

MongoDB事务机制

事务机制 1.事务概念 在对数据的操作的过程中&#xff0c;涉及到一连串的操作&#xff0c;这些操作如果失败&#xff0c;会导致我们的数据部分变化了&#xff0c;部分没变化。这个过程就好比如你去吃早餐&#xff0c;你点完餐了&#xff0c;并且吃完早餐了&#xff0c;没付钱你…

【文件包含】——日志文件注入

改变的确很难&#xff0c;但结果值得冒险 本文主要根据做题内容的总结&#xff0c;如有错误之处&#xff0c;还请各位师傅指正 一.伪协议的失效 当我们做到关于文件包含的题目时&#xff0c;常用思路其实就是使用伪协议&#xff08;php:filter,data,inpput等等&#xff09;执行…

职业技能大赛背景下的移动互联网应用软件开发(Android)实训室建设方案

一、建设背景 随着科技的持续进步&#xff0c;移动设备已成为人们日常生活中不可或缺的一部分。据相关数据&#xff0c;移动互联网的使用率在近年来显著上升。在这样的背景下&#xff0c;移动互联技术不仅推动了科技的发展&#xff0c;也渗透到了智能家居、车联网、工业自动化…