【二分查找】--- 二分模板总结

news2024/11/27 0:29:55

 Welcome to 9ilk's Code World

       

(๑•́ ₃ •̀๑) 个人主页:        9ilk

(๑•́ ₃ •̀๑) 文章专栏:     算法Journey


从本博客开始,博主将开始分享二分查找算法的相关知识。


🏠 朴素二分模板 ---  二分查找

📌 题目内容

二分查找

📌 题目解析

  本题是比较简单的二分查找,其中题中数组是有序的且元素不重复。

📌算法原理

✏️ 思路一:暴力解法

暴力解法思路很简单,其实就是遍历一遍数组找到target则退出,时间复杂度:O(N)

✏️ 思路二:二分查找

   在暴力解法的基础上,我们发现,当我们找到target的时候,由于整个数组有序,target能把整个数组分成两段,一段是小于target的,另一段是大于target的,由此我们说数组具有“二段性”。

何为二段性

当题目中能发现一个规律,根据这个规律选取某个区间或一个点,使得数组能够划分为两段区间,然后能舍弃一部分,这就是所谓的“二段性”。当我们发现具有二段性时,就可以使用二分查找。

划分方案的选择:

 既然目的是为了把区间划分为两段,那么其实不同的划分方案也是行的通的。

      

但是数学证明,选取1/2处的划分方案是最快的,因此我们常用的是二分。

朴素二分核心:

  • 当arr[mid] < target ,由于数组有序说明[left,target]区间的值都小于target,要向右查找
  • 当arr[mid] > target, 由于数组有序说明[mid,right]区间的值都大于target,要向左查找
  • 当arr[mid] == target时,此时找到返回结果。
细节问题:
  •  循环结束条件:

当区间不断缩小到只有一个数时,也就是left == right时,由于在这个过程中我们虽然能肉眼看出这是我们要的结果,但是程序仍然是要进入循环才能做出判断的,因此left <= right时我们进行循环查找。

  • 为什么二分查找是正确的

我们之前用暴力查找是正确的,这是毋庸置疑的,而我们的二分查找是利用了数组有序这个特性干着暴力查找的事,只不过我们不是一个一个查找,而是进行有效筛选后查找。

  • 二分查找的时间复杂度

1次折半缩小到n/2范围

2次折半缩小到n/4范围

3次折半缩小到n/8范围

....

x次折半缩小到1个数

==>n  = 2的x次方 ==> x = logn

==>时间复杂度为O(logn),查找了logn次。

  • 两种不同的划分中点方式
//1 int mid = (left + right) / 2;
//2 int mid = left + (right - left) / 2;
//3 int mid = left + (right - left + 1) / 2;

对于第一种方式我们并不建议,如果两个数都很接近整形最大,此时相加可能会发生溢出。

对于第二种和第三种方式,如果数据个数是偶数个他们选取到的中点有所不同,但我们的目的是找到点划分范围,因此并不影响选哪种。

参考代码:

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

📌 总结朴素二分算法模板

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

注:...表示具体问题具体分析。

🏠 查找左边界及右边界的二分模板

📌题目内容

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

📌题目解析

  • 题目中的数组是非递减的。
  • 题目中的数组存在重复数字。

📌 算法原理

✏️ 思路一:朴素二分再分类讨论

  我们朴素二分可以找到一个目标值,但题目要求我们找到两个相同的目标值,一个是第一次出现,另一个是第二次出现,此时我们可以先用二分不管在哪个位置找出一个再分类讨论位置找两个端点:

  • target只出现一次,直接返回当前位置
  • 找到的target是连续target中的左端点。
  • 找到的target是连续target中的右端点
  • 找到的target是连续target中的中间位置。

参考代码

class Solution 
{
public:
typedef long long ll;
    vector<int> searchRange(vector<int>& nums, int target)
    {
          vector<int> del = {-1,-1};
          if(nums.size() == 0)
          {
            return del;
          }
          int left = 0;
          int right = nums.size()-1;
          vector<int> v;
          bool flag = false;
          int mid = 0;
          while(left <= right)
          {
            mid = left + (right-left) / 2;
             if(nums[mid] < target)
             {
                  left = mid + 1;
             }
             else if(nums[mid] > target)
             {
                  right = mid - 1;
             }
             else
             {
                 flag = true;
                 break;
             }
          }
          if(!flag)
            return del; //表示没找到
         if(mid-1 > 0 && mid+1 <nums.size() &&nums[mid-1] != target && nums[mid+1] != target)
         {
              return vector<int>({mid,mid});
         }
         else if((mid-1 > 0 && mid+1 <nums.size()  && nums[mid-1] != target && nums[mid+1] == target) || mid == 0)
         {
             int cur = mid+1;
             while(cur < nums.size())
             {
                 if(nums[cur] == target)
                  cur++;
                 else
                 break; 
             }
            return vector<int>({mid,cur-1});
           
         }
         else if((mid-1 > 0 && mid+1 <nums.size()  && nums[mid-1]== target && nums[mid+1] != target) || mid == nums.size() -1)
         {
             int cur = mid-1;
             while(cur >= 0)
             {
                 if(nums[cur] == target)
                  cur--;
                 else
                 break; 
             }
            return vector<int>({cur+1,mid});
         }
       else
       {
            int cur1 = mid+1;
            int cur2 = mid-1;
            while(cur1 < nums.size())
            {
                if(nums[cur1] == target)
                    cur1++;
                else
                break;    
            }   
            while(cur2 >=0)
            {
                if(nums[cur2] == target)
                cur2--;
                else
                break;
            }
            return vector<int>({cur2+1,cur1-1});


       }

    }

};

代码又臭又长,有没有办法先找出左端点再找出右端点呢?

✏️ 思路二:边界二分

📒 区间左端点

  • 找区间左端点时我们仍可以利用二段性,我们发现左端点把整个数组划分为左边部分小于target,右边部分大于等于target.
  • 当mid处的值小于target时,此时说明在小于target的区间内,我们需要向右寻找,因此是left = mid +1.
  • 当mid处的值大于等于target时,此时说明在大于等于target的区间内,此时要找左端点我们需要向左边寻找,向左缩小范围,又由于x可能正好就是左端点,因此更新right时只能将right更新到mid的位置,如果是right = mid-1,[left,right]就没有左端点了,毕竟[mid,right]区间内的值都是大于等于target的。
细节处理:
  • 循环条件

对于【left,right】区间内值的情况我们可以分下列三种情况:

1. 对于第一种情况,[left,right]区间内有我们要找的左端点,此时left每一次移动是为了跳出小于target的区间,而right每一次移动是为了逼近左端点,因此最终left == right时就是我们要找的左端点。

2.对于第二种情况,如果区间全是target的,此时right会一直向左逼近,因为mid都是大于target的,从而最后到达left的位置,如果是left <= right的话,right到达left的位置时,mid一直是left的位置从而导致死循环。

3.第三种情况类似第二种,left一直逼近right直到到达right的位置,如果判断条件是left<=right的话也会导致死循环。

因此得出:

  1. left == right时就是最终结果无需判断。
  2. 如果判断就会导致死循环。
  • 求中点的操作

我们前面说明了求中点防溢出有两种求法

1.当采取第一种方式求中点时,此时mid求到的是left位置,由于left位置是小于target的,因此此时mid位置是小于target的,left会移动right处(mid+1)从而退出循环。

2.当采用第二种方式求中点时,此时mid求到的是right位置 ,由于right位置是大于等于target的,因此此时会一直right = mid陷入死循环。

因此得出:求左端点时,我们采用left + (right - left)/2的方式。

📒 区间右端点

  • 找区间右端点时我们仍可以利用二段性,我们发现右端点把整个数组划分为左边部分小于等于target,右边部分大于target.
  • 当mid处的值大于target时,此时说明在大于target的区间内,我们需要向左寻找,因此是right= mid - 1.
  • 当mid处的值小于等于target时,此时说明在小于等于target的区间内,此时要找右端点我们需要向右边寻找,向右缩小范围,又由于x可能正好就是右端点,因此更新left时只能将left更新到mid的位置,如果是left = mid+1,[left,right]就没有左端点了毕竟[left,mid]区间内的值都是小于等于target的。
细节处理
  • 循环条件

同分析左端点一样,循环条件也需要是left < right否则陷入死循环。

  • 求中点

1.当采取第一种方式求中点时,此时mid求到的是left位置,由于left位置是小于target的,因此此时mid位置是小于target的,left会一直left=mid陷入死循环

2.当采用第二种方式求中点时,此时mid求到的是right位置 ,由于right位置是大于target的,此时mid位置大于target,right会移动到left位置从而退出循环.

因此得出:求区间右端点时,采用left +(right-left+1)/2的方式。

因此对于本道题我们可以分别用这两种方法求出左右边界,参考代码如下:

class Solution 
{
public:
typedef long long ll;
    vector<int> searchRange(vector<int>& nums, int target)
    {
        vector<int> del = { -1,-1 };
        if (nums.size() == 0)
        {
            return del;
        }
        //求左端点
        int left = 0;
        int right = nums.size() - 1;
        int leftmid = 0;
        while (left < right)
        {
            leftmid = left + (right - left) / 2;
            if (nums[leftmid] < target)
            {
                left = leftmid + 1;
            }
            else
            {
                right = leftmid;
            }
        }
        if (nums[left] != target)
            leftmid = -1;
        else
            leftmid = left;
            //求右端点
        left = 0;
        right = nums.size() - 1;
        int rightmid = 0;
        while (left < right)
        {
            rightmid = left + (right - left + 1) / 2;
            if (nums[rightmid] > target)
            {
                right = rightmid - 1;
                cout << right << " " << endl;
            }
            else
            {
                left = rightmid;
            }
        }
        if (nums[left] != target)
            rightmid = -1;
        else
            rightmid = left;
        return vector<int>({ leftmid,rightmid });

    }

};

时间复杂度:O(logN)

📌 总结查找左边界及右边界二分模板

查找左边界:

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(...) left = mid;
   else right = mid - 1;
}

总结:

本博客我们讲解了朴素二分模板以及边界二分模板,朴素二分模板应用比较局限,而对于边界二分模板我们更常用.对于边界二分模板,我们要处理好它的循环条件以及求中点,同时根据我们求的左端点还是右端点来更新left和right.

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

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

相关文章

【Java日志系列】Log4j日志框架

目录 前言 一、Log4j简介 二、Log4j组件介绍 1. Loggers 2. Appenders 3. Layouts 三、快速入门 1. 入门代码 2. 日志级别 四、配置文件的使用 五、自定义日志输出格式 六、配置不同的Appender 1. 输出到文件 2. 输出到数据库 七、自定义Logger配置 总结 前言…

用 js canvas 做一个优雅的模拟时钟, canvas 教程实例

用 js canvas 做一个优雅的模拟时钟&#xff0c; canvas 教程实例 有很多次&#xff0c;我都想找到一个比较不错的&#xff0c;可以查看模拟时钟的网页。 有时候是想看下距离某个时间点还有多长时间&#xff0c;有时候是想看一下&#xff0c;两个时间点之间的间隔是多少。因为…

迅为i.MX8MM开发板控制GPIO高低电平-使用命令控制GPIO

在文件系统的/sys 目录下给用户提供了许多接口&#xff0c;比如在/sys 下面的 bus 目录&#xff0c;这个目录下放置的都是 和总线相关的&#xff0c;比如 I2C&#xff0c;usb 等。如下图所示&#xff1a; 本章我们需要重点关注下 class 目录&#xff0c;这个目录下放置的是一…

MyBatis-Plus 进阶之条件构造器Wrapper和常用接口

目录 1.wrapper 介绍 1.1什么是 wrapper 2.QueryWrapper 2.1测试前的准备 2.2例1&#xff1a;组装查询条件 2.3例2&#xff1a;组装排序条件 2.4例3&#xff1a;组装删除条件 2.5例4&#xff1a;条件的优先级 2.6例5&#xff1a;组装 select 子句 2.7例6&#xff1a;…

Redis常见的数据类型和应用场景

目录 Redis概述 简介 特点 架构 使用场景 Mermaid图示 Redis数据类型 基础数据类型 扩展数据类型&#xff08;Redis 3.2及以上版本&#xff09; Mermaid图示 String类型详解 定义 内部实现 应用场景 Mermaid图示 List类型详解 定义 内部实现 应用场景 Merma…

旧数据与新系统 —— 重现数据迁移之旅

某天&#xff0c;正做卡呢。突然收到客户通知&#xff1a;不用做了&#xff0c;这系统不要了。 啊? 都搞了好几年&#xff0c;说不要就不要了&#xff0c;客户这么财大气粗&#xff1f; 细问之下&#xff0c;原来变化来源于最新的商业决策——客户收购了一个市场占有率比较大…

基于JSP的社区疫情防控管理信息系统

你好&#xff0c;我是专注于计算机技术研究的学姐。如果你对社区疫情防控管理信息系统感兴趣或有相关需求&#xff0c;欢迎私信交流。 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;JSPJavaBeansServlet 工具&#xff1a;MyEclipse, Tomcat 系统展示…

仅12%程序员担心被AI取代 62%开发者在使用AI工具

**根据Stack Overflow近日发布的2024年开发者调查报告&#xff0c;只有12%的开发者认为AI威胁到了他们当前的工作&#xff0c;而高达70%的受访者已经将AI工具整合到了自己的工作流程中。**该调查共有超过6.5万名开发者参与&#xff0c;结果显示&#xff0c;使用AI工具的开发者比…

USBCAN-II/II+使用方法以及qt操作介绍

一.USBCAN-II/II介绍 USBCAN-II/II 是一款常用的 USB-CAN 转换器&#xff0c;广泛应用于汽车电子、工业自动化等领域。以下是使用该设备的一般步骤和方法&#xff1a; 1. 硬件连接 连接设备&#xff1a;将 USBCAN-II/II 的 USB 接口连接到计算机的 USB 端口。 连接 CAN 网络…

多功能声学气膜馆:解决城市噪音难题的标杆建筑—轻空间

在现代城市中&#xff0c;噪音污染已成为一个普遍存在且难以解决的问题。随着城市人口的增加和交通的繁忙&#xff0c;如何为市民提供一个安静、舒适的活动空间&#xff0c;成为城市规划者和建筑设计师亟待解决的挑战。多功能声学气膜馆&#xff0c;作为一种创新的建筑形式&…

只有IP如何实现https访问

IP也是访问网站的一种方式&#xff0c;现在有很多网站并未绑定域名&#xff0c;而是通过IP直接访问的。 但是域名访问网站的方式会更多一些&#xff0c;主要还是因为域名相较于IP数字要更加好记&#xff0c;所以域名绑定网站的情况会更多。 随着现在网络安全意识的逐渐提升&a…

Android MediaRecorder 视频录制及报错解决

目录 一、start failed: -19 二、使用MediaRecorder录制视频 2.1 申请权限 2.2 布局文件 2.3 MediaRecordActivity 2.4 运行结果 三、拓展 3.1 录制视频模糊(解决) 3.2 阿里云OSS上传文件 3.2.1 权限(刚需) 3.2.2 安装SDK 3.2.3 使用 相关链接 一、start failed…

[每周一更]-(第110期):QT开发最佳实战(php/go/python/javascript)

文章目录 1. 使用 Go 开发 Qt 应用工具&#xff1a;therecipe/qt安装和配置示例代码 2. 使用 Python 开发 Qt 应用工具&#xff1a;PyQt 或 PySide安装和配置示例代码&#xff08;PyQt5&#xff09; 3. 使用 PHP 开发 Qt 应用工具&#xff1a;PHP-Qt安装和配置示例代码 4. 使用…

VLL基本原理

VLL的基本架构 VLL技术通过隧道承载CE&#xff08;Customer Edge&#xff09;端的各种二层业务&#xff0c;透明传递CE端的二层数据&#xff0c;为用户提供点对点的二层VPN服务。 VLL的基本架构可以分为AC、VC和Tunnel三个部分&#xff0c;而PW这个概念也会经常用到。如图1所…

工控接入网关:实现工业物联网的关键技术

工业控制系统随着科技的发展&#xff0c;逐渐向数字化、智能化的方向发展。工控接入网关作为工业物联网的关键技术之一&#xff0c;具有重要的作用和意义。本文将从工控接入网关的定义、功能、应用、优势等方面进行全面详细地介绍。    一、工控接入网关的定义与功能 工控接…

新零售社交电商系统前景分析

新零售社交电商系统前景分析 新零售社交电商系统作为数字经济的创新趋势之一&#xff0c;展现出强大的市场活力和发展潜力。 以下是对其前景的分析&#xff1a; 市场规模的增长&#xff1a;中国社交电商的交易规模在2021年达到了23785.7亿元&#xff0c;并预计在2023年将增长…

面了拼多多算法岗,被疯狂拷打。。。

暑期实习基本结束了&#xff0c;校招即将开启。 不同以往的是&#xff0c;当前职场环境已不再是那个双向奔赴时代了。求职者在变多&#xff0c;HC 在变少&#xff0c;岗位要求还更高了。 最近&#xff0c;我们又陆续整理了很多大厂的面试题&#xff0c;帮助一些球友解惑答疑&…

8-4 循环神经网络

对于 (8.4.2)中的函数 f f f&#xff0c;隐变量模型不是近似值。 毕竟 h t h_{t} ht​是可以仅仅存储到目前为止观察到的所有数据&#xff0c; 然而这样的操作可能会使计算和存储的代价都变得昂贵。 回想一下&#xff0c;我们在前面讨论过的具有隐藏单元的隐藏层。 值得注意的…

VS Code安装配置ssh服务结合内网穿透远程连接本地服务器详细步骤

文章目录 前言1. 安装OpenSSH2.VS Code配置ssh3. 局域网测试连接远程服务器4. 公网远程连接4.1 ubuntu安装cpolar内网穿透4.2 创建隧道映射4.3 测试公网远程连接 5. 配置固定TCP端口地址5.1 保留一个固定TCP端口地址5.2 配置固定TCP端口地址5.3 测试固定公网地址远程 前言 远程…

Windows系统开机出现Minimal BASH-like line editingis supported解决方法

Minimal BASH-like line editingis supported解决方法 一、问题描述 因为电脑不知道是不是安装了双系统&#xff0c;开机的时候会出现标题中的这个问题&#xff0c;网上查找资料一般说是双系统的问题&#xff0c;引导项缺失&#xff0c;然后Ubuntu进不去。可是我的电脑好像没有…