【算法杂货铺】二分算法

news2025/3/16 18:00:37


目录

🌈前言🌈

📁 朴素二分查找

 📂 朴素二分模板

📁 查找区间端点处

细节(重要)

 📂 区间左端点处模板

 📂 区间右端点处模板

📁 习题

1. 35. 搜索插入位置 - 力扣(LeetCode)

2. 69. x 的平方根 - 力扣(LeetCode)

3.153. 寻找旋转排序数组中的最小值 - 力扣(LeetCode)

4. LCR 173. 点名 - 力扣(LeetCode)        

5. 852. 山脉数组的峰顶索引 - 力扣(LeetCode)

6.162. 寻找峰值 - 力扣(LeetCode)

📁 总结

🌈前言🌈

        欢迎观看本期【算法杂货铺】,本期内容将讲解基础算法中的——二分算法。二分查找算法,就是利用二段性,将问题划分成两个区间,去掉一个区间,在另一个区间查找答案。

        二分查找是有模板供大家使用的,即背下模板,只需要略微修改即可,但本文旨在理解这些模板。

        本篇文章通过讲解例题,带大家快速了解二分算法,分为3步,讲解各个二分算法。1. 题目解析;2. 算法思路;3. 代码展示。

📁 朴素二分查找

704. 二分查找 - 力扣(LeetCode)

1. 题目解析

        相信有一点语言基础的人都会这道题目,我们只需要遍历一遍这个数组即可,如果找到范围下标,没有找到返回-1,但是这种暴力解法,时间复杂度是O(N)。

        这里我们还有一个初始条件没有用到,即n个元素使有序的(升序)。

2. 算法原理

        上图中,我们通过在一个数组中查找7,给大家展示了二分算法的基本思路。即根据某种规则,将区间划分成两端,再根据规则,舍去一端区间,在另一端区间内查找。

结束条件:

        当 left > right 的时候,不存在区间,循环结束。为什么left可以等于right呢,当只有1个元素的时候,也是要查找的 , 即 (left + right )/2 = right = left 的。

        

为什么是正确的:

        二分算法是利用二段行进行查找的,这道题的二段性体现在数组是有序的。在暴力算法中,我们是一个个比较的,在二分算法中,我们是按区间比较的,即[ left , mid-1 ] 和 [ mid+1 , right]。

时间复杂度:

        我们只需要看循环了多少次即可,只要left <= right的时候,就进循环,当问题规模n就除2。

          当left == right的时候,数组中只剩下一个元素,所以执行x次后,问题规模就为1。​​​​​​​

3. 代码展示

class Solution {
public:
    int search(vector<int>& nums, int target) {

        int left = 0;
        int right = nums.size() - 1;
        while(left <= right)
        {
            //优化:防止溢出
            int mid = left + (right - left)/2;
            if(nums[mid] == target)
            {
                return mid;
            }
            else if(nums[mid] > target)
            {
                right = mid - 1;
            }
            else{
                left = mid + 1;
            }
        }
        return -1;
    }
};

 📂 朴素二分模板

while(left <= right)
{
    //优化:防止溢出
    int mid = left + (right - left)/2
    if(...)
        left = mid + 1;
    else if(...)
        right = mid - 1;
    else
        return mid;
}

        mid = left + (right - left) /2 是进行了优化的,因为当left 和 right都很大时,很容易超出int类型的范围。mid = left + (right - left + 1) / 2在朴素二分模板中也是可以的。

        +1的区别在于,偶数个数时,是向上取整,还是向下取整。

向下取整:left + ( right - left ) / 2

向下取整:left + (right - left + 1) / 2

📁 查找区间端点处

34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode)

1. 题目解析

        依旧先从简单看起,暴力解法就是先从前往后遍历,找到第一个位置,如果找到,接着从后往前遍历,查找最后一个位置,范围begin和end。否则返回-1,-1。

        暴力解法的时间复杂度是O(N)的,但题目要求我们在O(logN)的时间复杂度。在基础算法中,很少有算法能优化的logN,二分算法就是其中之一。

        这道题目也是一道非常经典的二分查找算法,即查找区间的左端点,和区间的右端点。

2. 算法原理

        用的还是二分算法,即二段行,根据数据的性质,在某种判断条件下将区间一分为二(不一定是有序,有二段性即可。)舍去一个区间,在另一个区间内查找。

寻找左端点思路:

        左区间:[ left , result - 1 ] 都是严格小于 t 的。

        右区间: [ result , right ] 都是严格大于等于 t 的。

​​​​​​​

        因此,关于mid的落点,我们可以分为下面这两种情况:

        1. 当 mid 落在[ left , result - 1 ] 区间的时候,即 nums[mid] < t 。说明 [left , mid ]都是可以舍去的,此时更新left到mid+1的位置,继续在 [ mid +1 , right] 上寻找左边界。

        2. 当mid 落在[result , right]区间的时候,即 nums[mid] >= t。说明[mid +1 ,right]的区间可以社区(mid可能是最终结果,也可能不是),继续在[left,mid]上查找左端点。

注意的是,寻找左端点时,采用向下取整

        左指针的变化:left = mid + 1,区间逐渐减少。

        右指针的变化:right = mid ,可能会原地踏步。如果采用向上取整,可能会死循环,例如:t = 2 , mid = left + (right - left + 1)/2的话,right = mid,mid = right陷入死循环。

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

 寻找右端点思路:

       左区间:[ left , result ] 都是严格小于等与t的。

       右区间:[ result+1 , right ] 都是严格大于t的。

        1. 当mid落在左区间时,mid可能是最终结果,也可能不是,所以 left = mid,舍去区间[left,mid -1]。继续在区间[ mid , right]区间内查找。

        2. 当mid落在右区间时,可以舍去[mid , right]的区间,right = mid - 1。继续在区间[ left , mid -1 ]内查找。

注意的是,寻找右端点时,采用向上取整

        右指针的变化:right = mid - 1,区间逐渐减少。

        左指针的变化:left = mid,可能会原地踏步,采用向下取整,可能会陷入死循环。例如:t =1 ,mid = left + (right - left) / 2

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

细节(重要)

 (1) 循环条件 

                left < right。我们以寻找右端点为例:

        left的工作区间就是在小于等于t这个区间;right的工作区间就是在大于t这个区间。因为right = mid - 1,所以right跳出工作区间时,一定是在right == left的位置。

        所以不需要判断right是否等于left,是一定等于的。

​​​​​​​

(2) 求中点操作  

        其实,我们只需要记住,下面有-1的时候,上面就有+1,。其他的结合题意给出二段行的判断条件。

3. 代码展示

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        if(nums.size() == 0)
        {
            return {-1,-1};
        }

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

        int begin = left;

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

 📂 区间左端点处模板

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

 📂 区间右端点处模板

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

        以上,就将二分算法的所有知识点讲解完毕,其中朴素二分模板是容易理解的,后面两个二分模板还是需要大家自己画图不断加强理解的。

        下面,我们通过几道习题加强理解。对于习题,不会再过多讲解知识点内容,而是直接给出题解思路,并配图加强理解和做题能力。

📁 习题

1. 35. 搜索插入位置 - 力扣(LeetCode)

        我们通过画图,可以看出来,这是一道非常经典的查找区间左端点的题,找到大于等于t的第一个点即可。        

        如果t不存在在数组中,则返回left+1(left 经过循环来到right这个位置,即最后一个位置)

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

2. 69. x 的平方根 - 力扣(LeetCode)

        其实这道题,你用二分查找左端点,或者查找右端点都可以做出来。这里我们就以查找右端点为例,当然,也会展示左端点的解法。

        前面需要特殊判断x是否小于1,如果小于1,则返回0。

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

        以上是查找右端点的写法,当然也可以写成查找左端点。

        不过因为,查找左端点,数值是会比查找右端点大的多,所以,left 和 right都采用 long long类型。最后判断一下,right的平方是否等于x,如果不等于返回right-1。

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

        这道题,可以很好的理解二分法,即一道题往往不止一种解决方法。

3.153. 寻找旋转排序数组中的最小值 - 力扣(LeetCode)

        通过题意,可以了解的是,旋转就是将最后一个元素移动到最开始的位置。此外,还有一个重要条件就是 互不相同

        那我们就可以利用互不相同,得出二段行,如下图所示。

         因此,我么以D点的值为索引,

        当mid落在区间[A,B]时,left = mid + 1。[A,B]区间内的点是严格大于D点的值。

        当mid落在区间[C,D]时,right = mid。其中C点就是我们要求的点。C点的值是要个小于D点的值。当[C,D]区间内只有一个元素时,C点的值可能等于D点的值。

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

4. LCR 173. 点名 - 力扣(LeetCode)        

        ​​​​​​​数组中,下标等于元素值,如果有一位同学缺席,那么它之后所有元素的下标就不等于元素的值。

        因此,我们只需要查找左端点即可。

        此外,这道题还需要注意的是,最后需要处理一下边界情况,如数组中仅有一个元素1,那么缺席的同学编号就是0;如果仅有一个元素0,那么缺席的同学编号为1。

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

5. 852. 山脉数组的峰顶索引 - 力扣(LeetCode)

        这道题目,查找左端点或查找右端点都可以,这里我们就以查找右端点为例,不在讲解如何查找左端点,感兴趣可以自己实践一下。

​​​​​​​

        当mid处于上升区间时,在[mid + 1, right]区间查找。

        当mid处于下降区间时,在[left , mid - 1]区间查找。

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

6.162. 寻找峰值 - 力扣(LeetCode)

 寻找⼆段性:

        1. 当arr[mid] > arr[mid+1],左侧一定存在山峰,到[left,mid]寻找山峰。

        2. 当arr[mid] < arr[mid+1],右侧一定存在山峰,到[mid+1,right]寻找。

class Solution {
public:
    int findPeakElement(vector<int>& nums) {
        int left =0;
        int right = nums.size()-1;
        while(left < right)
        {
            int mid = left +  (right - left) / 2;

            if(nums[mid] < nums[mid+1])
            {
                left = mid + 1;
            }
            else
            {
                right = mid;
            }
        }
        return left;
    }
};

📁 总结

        以上,我们就将二分算法带大家做了全面了解。二分算法的核心就是寻找二段性,有了二段性,直接套模板即可。对于模板,建议是理解着去背诵,效果更好。

        通过习题,我们也加强了对二分算法的理解,平时做题中,看到时间复杂度为logN的,我们应该敏锐的想到二分算法。

        以上就是本期【算法杂货铺】的主要内容了,如果感觉对你有帮助,欢迎点赞,收藏,关注。Thanks♪(・ω・)ノ

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

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

相关文章

实验01 ASP.NET网站的建立及运行

【实验目的】 &#xff08;1&#xff09;能熟悉ASP.NET的开发环境Visual Studio Community 2019&#xff08;VSC 2019&#xff09;。 &#xff08;2&#xff09;能通过解决方案管理网站&#xff0c;会在解决方案中创建网站。 &#xff08;3&#xff09;会设置IIS 10中的网站…

德迅蜂巢(容器安全)全面出击

随着云计算的发展&#xff0c;以容器和微服务为代表的云原生技术&#xff0c;受到了人们的广泛关注&#xff0c;德迅云安全德迅蜂巢&#xff08;容器安全&#xff09;是企业容器运行时和容器编排的首要选择。然而&#xff0c;在应用容器过程中&#xff0c;大多数企业都遇到过不…

VS2022 配置QT5.9.9

QT安装 下载地址&#xff1a;https://download.qt.io/archive/qt/ 下载安装后进行配置 无法运行 rc.exe 下载VS2022 官网下载 配置 1.扩展-管理扩展-下载Qt Visual Studio Tools 安装 2.安装完成后&#xff0c;打开vs2022,点击扩展&#xff0c;会发现多出了QT VS Tools,点…

应用层_HTTPHTTPS

在应用层中&#xff0c;协议一般是程序员定制的&#xff0c;但现在已经有了许多非常好用的协议&#xff0c;我们可以直接参考使用。其中http和https便是其中最常用的协议之一。 一.HTTP 超文本传输协议&#xff08;Hypertext Transfer Protocol&#xff0c;HTTP&#xff09;…

Unreal发布Android App如何面对混乱的Android SDK开发环境

Unreal发布Android App如何面对混乱的Android SDK开发环境 混乱的Android SDK开发环境Unreal 4可以借用Unity3D安装的Android环境Unreal 5需要安装Android Studio开发环境Android Studio的DK版本目录处理gradle和java版本gradle提示错误总结 混乱的Android SDK开发环境 Unreal…

ubuntu安装zsh及环境配置

ubuntu安装zsh及环境配置 MacBook 安装 zsh 个人很喜欢使用zsh,它的终端显示很清晰,命令都很友好,使用git时,直接可以看到当前分支和修改状态 zsh安装 1.查看当前系统装了哪些shellcat /etc/shells 2.当前正在运行的是哪个版本的shellecho $SHELL 3.安装zshsudo apt-get -y …

深入理解TCP:序列号、确认号和自动ACK的艺术

深入理解TCP&#xff1a;序列号、确认号和自动ACK的艺术 在计算机网络的世界里&#xff0c;TCP&#xff08;传输控制协议&#xff09;扮演着至关重要的角色。它确保了数据在不可靠的网络环境中可靠地、按顺序地传输。TCP的设计充满智慧&#xff0c;其中序列号&#xff08;Seq&a…

html和winform webBrowser控件交互并播放视频(包含转码)

1、 为了使网页能够与winform交互 将com的可访问性设置为真 [System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Demand, Name "FullTrust")][System.Runtime.InteropServices.ComVisibleAttribute(true)] 2、在webBrow…

MS08-067 漏洞利用与安全加固

文章目录 环境说明1 MS08_067 简介2 MS08_067 复现过程3 MS08_067 安全加固 环境说明 渗透机操作系统&#xff1a;2024.1漏洞复现操作系统: Windows XP Professional with Service Pack 2- VL (English)安全加固复现操作系统&#xff1a;Windows XP Professional with Service …

一文让您读懂实时数仓(Apache Doris)

引言&#xff1a; 随着大数据时代的来临&#xff0c;实时数据处理与分析成为企业核心竞争力的关键因素之一。在这场数据革命中&#xff0c;SelectDB成为引领者。从百度自研的实时数仓平台 Palo&#xff0c;到开源项目 Apache Doris&#xff0c;再到飞轮科技研发的 SelectDB&am…

鸿蒙Harmony应用开发—ArkTS声明式开发(容器组件:ListItem)

用来展示列表具体item&#xff0c;必须配合List来使用。 说明&#xff1a; 该组件从API Version 7开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。该组件的父组件只能是List或者ListItemGroup。 子组件 可以包含单个子组件。 接口 从API…

19113133262(微信同号)【征稿进行时|见刊、检索快速稳定】2024年区块链、物联网与复合材料与国际学术会议 (ICBITC 2024)

【征稿进行时|见刊、检索快速稳定】2024年区块链、物联网与复合材料与国际学术会议 (ICBITC 2024) 大会主题: (主题包括但不限于, 更多主题请咨询会务组苏老师) 区块链&#xff1a; 区块链技术和系统 分布式一致性算法和协议 块链性能 信息储存系统 区块链可扩展性 区块…

[云原生] Prometheus之部署 Alertmanager 发送告警

一、Alertmanager 发送告警的介绍 Prometheus 对指标的收集、存储与告警能力分属于 Prometheus Server 和 AlertManager 两个独立的组件&#xff0c;前者仅负责定义告警规则生成告警通知&#xff0c; 具体的告警操作则由后者完成。 Alertmanager 负责处理由 Prometheus Serve…

SQLiteC/C++接口详细介绍之sqlite3类(九)

返回目录&#xff1a;SQLite—免费开源数据库系列文章目录 上一篇&#xff1a;SQLiteC/C接口详细介绍之sqlite3类&#xff08;八&#xff09; 下一篇&#xff1a;​​SQLiteC/C接口详细介绍之sqlite3类&#xff08;十&#xff09;&#xff08;未发表&#xff09;​​​​ 27…

机器学习概念(一)

机器学习 是一门使计算机在没有明确编程的情况下具备学习能力的研究领域。 监督学习&#xff08;Supervised learning&#xff09; 监督学习算法 通过学习输入&#xff08;x&#xff09;到输出&#xff08;y&#xff09;的映射关系。在监督学习中&#xff0c;你需要为算法提…

AI日报:欧盟人工智能法案通过后行业面临合规障碍

文章目录 人工智能新规对web爬网的影响对英国的影响。 人工智能新规 立法者已经通过了欧盟人工智能法案。企业现在必须确保其人工智能应用程序符合规则。 全面的新规定对可能影响公民权利的人工智能系统实施制裁&#xff0c;并有可能彻底禁止某些系统。 违反规定的公司可能面…

stable diffusion上安装数字人sadtalker插件

数字人无论是在营销还是品牌推广的作用都非常重要&#xff0c;很多企业和个人都正在使用数字为自己创作财富&#xff0c;市面上的数字人生成网站包括某讯智影、D-ID或者是Heygen收费都比较昂贵。 那么如何才能拥有一个免费的数字人生成工具呢&#xff1f;其实很简单你只需要在…

基于LBP和KNN的人面表情识别,Matlab实现

博主简介&#xff1a; 专注、专一于Matlab图像处理学习、交流&#xff0c;matlab图像代码代做/项目合作可以联系&#xff08;QQ:3249726188&#xff09; 个人主页&#xff1a;Matlab_ImagePro-CSDN博客 原则&#xff1a;代码均由本人编写完成&#xff0c;非中介&#xff0c;提供…

Unity类银河恶魔城学习记录10-10 p98 UI health bar源代码

Alex教程每一P的教程原代码加上我自己的理解初步理解写的注释&#xff0c;可供学习Alex教程的人参考 此代码仅为较上一P有所改变的代码 【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili HealthBar_UI.cs using System.Collections; using System.Collections.G…

AJAX学习(四)

版权声明 本文章来源于B站上的某马课程&#xff0c;由本人整理&#xff0c;仅供学习交流使用。如涉及侵权问题&#xff0c;请立即与本人联系&#xff0c;本人将积极配合删除相关内容。感谢理解和支持&#xff0c;本人致力于维护原创作品的权益&#xff0c;共同营造一个尊重知识…