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

news2024/11/19 13:37:23

目录

二分查找

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;
    }
};

总结一下:

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

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

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

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

相关文章

P3565 [POI2014] HOT-Hotels

~~~~~ P3565 [POI2014] HOT-Hotels ~~~~~ 总题单链接 ~~~~~ 2024.9.10&#xff1a;DP方程有问题&#xff0c;已修改&#xff0c;同时更新了长链剖分优化版本。 思路 ~~~~~ 设 g [ u ] [ i ] g[u][i] g[u][i] 表示在 u u u 的子树内&#xff0c;距离 u u u 为 i i i 的点的…

了解国产光耦合器的核心功能和应用

光耦合器或光隔离器是现代电子产品中的关键组件&#xff0c;它能够在没有电接触的情况下在系统的不同部分之间安全地传输信号。这是通过基于光的信号传输来实现的&#xff0c;它可以隔离电路&#xff0c;防止高压损坏敏感元件。近年来&#xff0c;国产光耦合器取得了重大进展&a…

孙怡带你深度学习(1)--神经网络

文章目录 深度学习神经网络1. 感知器2. 多层感知器偏置 3. 神经网络的构造4. 模型训练损失函数 总结 深度学习 深度学习(DL, Deep Learning)是机器学习(ML, Machine Learning)领域中一个新的研究方向。 从上方的内容包含结果&#xff0c;我们可以知道&#xff0c;在学习深度学…

OpenHarmony鸿蒙开发( Beta5.0)智能油烟机开发实践

样例简介 本Demo是基于Hi3516开发板&#xff0c;使用开源OpenHarmony开发的应用。本应用主要功能有&#xff1a; 可以搜索本地指定目录的图片和视频文件&#xff0c;并可进行点击播放。 可以通过wifi接收来自手机的美食图片以及菜谱视频&#xff0c;让我们对美食可以边学边做…

昨晚,OpenAI震撼发布o1大模型!我们正式迈入了下一个时代。

大半夜的&#xff0c;OpenAI抽象了整整快半年的新模型。 在没有任何预告下&#xff0c;正式登场。 正式版名称不叫草莓&#xff0c;草莓只是内部的一个代号。他们的正式名字&#xff0c;叫&#xff1a; 为什么取名叫o1&#xff0c;OpenAI是这么说的&#xff1a; For complex …

TCP核心机制

TCP基本特点&#xff1a;有连接&#xff0c;面向字节流&#xff0c;全双工&#xff0c;可靠传输(TCP最核心的机制) 核心机制一(确认应答): 在网络中&#xff0c;可能我们传输的消息会因为诸多原因导致发送到对方手中的顺序不一样&#xff0c;举个例子&#xff1a; 在这张图中…

【鸿蒙开发从0到1 支付宝界面布局实现day11】

鸿蒙开发案例-支付宝界面 一.布局思路二.页面搭建1.整体stack布局底部的tab2.主体区域的架子:头部主体界面(层叠关系,主题页面可以滚动)3.给主体内容填内容(1).完成快捷导航(2)服务导航 4.装饰图片 三.整体效果展示1.效果展示2.完整代码演示 四.总结 一.布局思路 整体stack布局…

成型的程序

加一个提示信息 加上python 常用的包 整个程序打包完 250M 安装 960MB matplot numpy pandas scapy pysearial 常用的包 (pyvisa)… … 啥都有 Python 解释器组件构建 要比 lua 容易的多 &#xff08;C/Rust 的组件库)

Vue3 父组件向子组件传值:异步数据处理的显示问题

一、问题场景 假设我们有一个父组件和一个子组件&#xff0c;父组件需要经过一些复杂的计算或者异步操作才能得到要传递给子组件的值。在数据还没有准备好的时候&#xff0c;子组件尝试获取并显示这个值&#xff0c;这就可能导致子组件没有数据可显示或者显示了一个不正确的初…

简单有趣的python小程序(涵源代码)

目录 tkinter 计算器 2.计算题练习 猜数字 烦人的程序 无法拒绝的请假条。。。 爬虫 你想看豆瓣评分前十的电影? WXpython 记事本&#xff08;可保存&#xff09;​编辑 数字逻辑 解方程 tkinter 计算器 import tkinter as tk import tkinter.messagebox as mroot…

使用ChatGPT撰写论文,一定要掌握加强理论深度的八个策略!

大家好,感谢关注。我是七哥,一个在高校里不务正业,折腾学术科研AI实操的学术人。关于使用ChatGPT等AI学术科研的相关问题可以和作者多多交流,相互成就,共同进步,为大家带来最酷最有效的智能AI学术科研写作攻略。 在学术论文的写作中,加强论文的理论深度是非常重要的一个…

nacos明明配置了远程连接地址却一直连接本地的详细配置解释

大家时间都很珍贵&#xff0c;我直接把方法放这 这个是yml文件&#xff0c;我们配置yml文件的时候&#xff0c;一定要把他的服务发现地址写了 这里是针对bootstrap做出的文件&#xff0c;注意名字&#xff0c;要和我们在yml文件里面的spring名字一样 yml discovery:是发现的意…

C到C++入门基础知识

一&#xff1a;命名空间&#xff1a;namespace &#xff08;一&#xff09;&#xff1a;命名空间的定义 注&#xff1a;命名空间只能定义在全局&#xff0c;不能定义在函数内部。 &#xff08;1&#xff09;类似于C语言的结构体&#xff0c;C语言的命名空间定义为&#xff1…

Java Enterprise System 体系结构

本章概述了 Java Enterprise System 部署所基于的体系结构概念。 章中描述了一个框架,在此框架内从三维角度对 Java Enterprise System部署体系结构进行了分析,它们分别是:逻辑层、基础结构服务级别和服务质量。这三维在下图中以图解形式显示为正交坐标轴,它们有助于在体系…

Word使用手册

修改样式 编辑word文档时&#xff0c;标题和正文文本通常有不同的格式&#xff0c;如果能将这些格式保存为样式&#xff0c;下一次就能直接调用样式&#xff0c;而不需要重复手动设置格式。 可以将样式通常保存为不同的 样式模板.docx&#xff0c;要调用不同样式集&#xff0…

看Threejs好玩示例,学习创新与技术

我把在一些好玩的ThreeJS的效果&#xff0c;认真分析技术&#xff0c;写成博客&#xff0c;欢迎大家去看。 后面慢慢补充。 看Threejs好玩示例&#xff0c;学习创新与技术(一)https://mp.weixin.qq.com/s/eJeGmnla0D4zEMl4AwFsVw

波克城市 x NebulaGraph|高效数据血缘系统在游戏领域的构建实战

关于波克城市和作者‍‍ 波克城市&#xff0c;一家专注于研发精品休闲游戏的全球化公司&#xff0c;连续七年入选中国互联网综合实力百强&#xff0c;2023 年位列 17 位。波克城市旗下拥有《捕鱼达人》《猫咪公寓2》等精品休闲游戏&#xff0c;全球注册用户超 5 亿&#xff0c;…

AB 1756-L62 与 AB 5069 通过串口通信

PLC AB L62 控制器 插槽2 Path, RS232=2, 3 PLC Compactlogix 5069-SERIAL 配置

【提示词】浅谈GPT等大模型中的Prompt

Prompt是人工智能&#xff08;AI&#xff09;提示词&#xff0c;是一种利用自然语言来指导或激发人工智能模型完成特定任务的方法。在AI语境中&#xff0c;Prompt是一种自然语言输入&#xff0c;通常指的是向模型提出的一个请求或问题&#xff0c;这个请求或问题的形式和内容会…

【QT】系统-上

欢迎来到Cefler的博客&#x1f601; &#x1f54c;博客主页&#xff1a;折纸花满衣 &#x1f3e0;个人专栏&#xff1a;QT 目录 &#x1f449;&#x1f3fb;事件QWidget中常见的事件 &#x1f449;&#x1f3fb;处理鼠标事件&#xff1a;leaveEvent和enterEvent&#x1f449;&a…