【数据结构与算法篇】还不会二分查找?看这篇就够了!

news2024/12/27 1:04:25

​👻内容专栏: 《数据结构与算法篇》
🐨本文概括:整数二分算法(朴素二分,查找区间左端点与区间右端点二分)、浮点数二分
🐼本文作者: 阿四啊
🐸发布时间:2023.10.22

目录

    • 二分查找(binary search)
      • 1.朴素二分查找:
        • 代码实现:
      • 2.二分查找优化
        • 查找区间的左端点
          • ⚠️细节问题处理:
            • 一、循环条件问题
            • 二、求中点(mid)问题
        • 查找区间的右端点
          • ⚠️细节问题处理:
            • 一、循环条件问题:
            • 二、求中点(mid)问题:
        • 二分模板总结
        • 代码实现:
      • 3.浮点数二分
    • References

二分查找(binary search)

1.朴素二分查找:

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

以上这一题可以利用暴力的方式,将数组遍历一遍,查找target的位置,时间复杂度为O(n),那么有没有高效的算法呢?

二分查找算法:时间复杂度为O(logN),前提认为数组为有序序列(单调性)即可(其实后面学习,前提并不是数组为单调性,而是区间具有二段性,也就是说按某种性质,可以将该数组分为两段区间)。

📌ps:那么假设一共4,294,967,296(2^32)个数据,暴力枚举的时间复杂度为4* 10^9 ,而二分查找就只需要32次了。

我们来看一张二分查找与遍历查找的效率对比图:

binary_search
代码实现:
class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left = 0, right = nums.size() - 1;
        while(left <= right)
        {
            int mid = left + (right - left) / 2; //防止溢出
            if(target < nums[mid]) right = mid - 1;
            else if(target > nums[mid]) left = mid + 1;
            else return mid;
        }
        return -1;
    }
};

2.二分查找优化

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

题目描述:

在这里插入图片描述


在这里插入图片描述

📌假设1,2,3,3,3,4,5这组数据,目标值为3,找到目标值在数组中的开始下标和结束下标。显然用朴素二分去做就很棘手了,因为并不能确定3是在起始位置还是终点位置,若在中间位置呢,还另外需要向前遍历向后遍历等于3的值,若极端的情况之下,变成3,3,3,3,3,3,3这组数据,那么朴素的二分就退化为暴力求解的时间复杂度了,在这里会讲解查找区间左端点(简称Search_Left)查找区间右端点(简称Search_Right)两个模板,根据结论记住模板即可,这里的记忆并不是死记硬背,而是需要理解边界处理的细节过程!!!

查找区间的左端点

🎗️以下target简述为t

假设我们需要先查找区间的左端点,那么端点左边的区域1,2一定是小于t的,端点右边区域包括该端点3,3,3,4,5大于等于t的。

在这里插入图片描述

  • 假设算出的mid下标所对应的元素为x

  • x < t,那么x肯定是在小于t的区间里,tx的右边,此时left需要更新为mid+1,然后再到[left,right]区间中继续查找;

    if(x < t) left = mid + 1;
    
  • xt,即 [mid, right]区域里的元素肯定是大于等t的,那么此时right需要更新为mid,然后再到[left,right]区间中继续查找;

    if(x >= t) right = mid;
    

在这里插入图片描述

⚠️细节问题处理:

以上为查找区间左端点的核心步骤了,但是重点是两处细节处理操作。

一、循环条件问题

这里的循环继续条件是,像朴素二分查找一样left <= right 还是left < right呢?答案是**left < right**,我们看以下分析:

为了保证说服力,我们假设给出三种情况的数据:1.数组中有结果(等于t的值)2.数组中全是大于t的值3.数组中全是小于t的值

  • 1.数组中有结果(图中这个结果ret为本次区间需要求的左端点t)。

    先看我们的right,前面说过,xt时,我们的right一定是在ret右边的区间里移动。x < t时,left执行的是left = mid + 1。等到left继续走之后,也就是left == right,此时指向的ret就是我们想要的结果。所以无需判断left == right的情况。

  • 2.数组中全是大于t的值

    数组中left指针指向的元素一定是大于t的,那么此时,我们的right会走前面第二种情况,一直执行right = mid操作,right指针会向前移动,等到left == right时, 跳出循环,判断此时的left or right是否等于t,是的话就返回结果,否则返回{-1,-1}即可。

    在这里插入图片描述

  • 3.数组中全是小于t的值

    数组中right指针指向的元素一定是小于t的,那么此时,left会执行前面第一种情况,一直执行left = mid + 1操作,left会向后移动,等到left == right时, 跳出循环,判断此时的left or right是否等于t,是的话就返回结果,否则返回{-1,-1}即可。

在这里插入图片描述

所以,循环条件为left < right我们无需进行left == right相等的情况, 若判断,就会出现死循环。

二、求中点(mid)问题

查找区间左端点中我们求mid应该使用left + (right - left) / 2向下取整,而不是 left + (right - left + 1) / 2向上取整。为什么呢?

假定为向上取整,会发生什么情况?下面我们来分析一下:

假如数组中只有两个元素,我们使用向上取整的方式求mid,此时mid会指向第二个元素,当程序走第二种情况right = mid,就会陷入死循环!

在这里插入图片描述

查找区间的右端点

假设我们需要先查找区间的右端点,那么端点左边区域包括该端点1,2,3,3,3小于等于t的,端点右边的区域4, 5一定是大于t的。
在这里插入图片描述

  • 同样的,我们假设算出的mid下标所对应的元素为x

  • x <= t,说明x是在小于等于t的区间里,此时我们需要变动leftx因为也可能会等于t,所以更新为left = mid,然后再到[left,right]区间中继续查找;

    if(x <= t) left = mid
    
  • x > t,说明x是落在大于t的区间里面,此时我们需要变动rightt至少为落在该区间的左边位置,所以更新为right = mid - 1,然后再到[left,right]区间中继续查找;

    if(x > t) right = mid - 1;
    

在这里插入图片描述

⚠️细节问题处理:
一、循环条件问题:

这里像找左端点的循环条件一样,也是left < right ,就不多说了。

二、求中点(mid)问题:

查找区间右端点中我们求mid应该使用 left + (right - left + 1) / 2向上取整,而不是left + (right - left) / 2向下取整。

假定为向下取整,会发生什么情况?下面我们来分析一下:

假如数组中只有两个元素,我们使用向下取整的方式求mid,此时mid会指向第一个元素,当程序走第一种情况left = mid,就会陷入死循环!
在这里插入图片描述

二分模板总结

🚩:以下是二分模板的总结,关于二分查找的模板我们最好去理解它,分类讨论,根据不同的题目场景去应用,而不是死记硬背。

🔗Get验证技巧:有(右)加必有(右)减,此口诀针对的是右端点模板,右加是求中点时+1右减是代码过程里有-1

在这里插入图片描述

代码实现:
class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        //特判一下
        if(nums.size() == 0) return {-1, -1};
         
        int left = 0, right = nums.size() - 1;
        while(left < right)
        {
            int mid = left + (right - left) / 2;
            if(nums[mid] < target) left = mid + 1;
            else right = mid;
        }
        int begin = 0;//用于存储查找的区间左端点值
        if(nums[left] == target) begin = left; 
        else return {-1, -1}; //不相等返回-1,-1即可

        right = nums.size() - 1;//更新right值,left值可以不用更新为0
        while(left < right)
        {
            int mid = left + (right - left + 1) / 2;
            if(nums[mid] > target) right = mid - 1;
            else left = mid;
        }
        int end = left;//用于存储查找区间的右端点值

        return {begin, end};
    }
};

3.浮点数二分

和前面的整数二分不同,浮点数不存在整数上下取整导致的边界问题,每次二分区间严格减半,因此,浮点数二分比整数二分简单得多,每次更新边界直接令 r = midl = mid即可。

790. 数的三次方根 - AcWing题库

浮点数二分除了更新区间和浮点数不同,还有一个细节就是二分终止条件,一般有两种写法,一种就是当前区间长度已经足够小。 比如这题需要保留六位小数,我们可以在区间长度小于1e-8时结束循环,一般区间长度比保留位数还要小两个数量级

#include<iostream>
using namespace std;

int main()
{
    double x;
    cin >> x;
    //数据的范围是-10000到10000,也可以写成-100到100
    double left = -10000, right = 10000;
    while(right - left > 1e-8)
    {
        double mid = (left + right) / 2;
        if(mid * mid * mid < x) left = mid;
        else right = mid;
    }
    printf("%.6lf",left);  
    return 0;
}

还有一种写法,就是直接把二分迭代100次,也就是把while(r - l > 1e-8)换成for(int i = 0; i < 100; ++i) 这句话的意思是把区间缩小2100倍,由于2100是个很大的数,所以这样也能让区间变得很小,也能得到我们的结果。

#include<iostream>
using namespace std;

int main()
{
    double x;
    cin >> x;
    
    double left = -10000, right = 10000;
    
    for(int i = 0;i < 100;i++)
    {
        double mid = (left + right) / 2;
        if(mid * mid * mid < x) left = mid;
        else right = mid;
    }
    printf("%.6lf",left);  
    return 0;
}

References

浮点数二分
LeetCode34:在排序数组中查找元素的第一个和最后一个位置
AcWing790:数的三次方根

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

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

相关文章

【Python数据挖掘 基础篇】Python数据挖掘是个啥?

作者&#xff1a;Insist-- 个人主页&#xff1a;insist--个人主页 梦想从未散场&#xff0c;传奇永不落幕&#xff0c;博主会持续更新优质网络知识、Python知识、Linux知识以及各种小技巧&#xff0c;愿你我共同在CSDN进步 目录 一、了解数据挖掘 1. 数据挖掘是什么&#xff…

【LeetCode:1402. 做菜顺序 | 动态规划 + 贪心】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

【Kotlin精简】第5章 简析DSL

1 DSL是什么&#xff1f; Kotlin 是一门对 DSL 友好的语言&#xff0c;它的许多语法特性有助于 DSL 的打造&#xff0c;提升特定场景下代码的可读性和安全性。本文将带你了解 Kotlin DSL 的一般实现步骤&#xff0c;以及如何通过 DslMarker &#xff0c; Context Receivers 等…

音乐制作软件 Studio One 6 mac中文版软件特点

Studio One mac是一款专业的音乐制作软件&#xff0c;该软件提供了全面的音频编辑和混音功能&#xff0c;包括录制、编曲、合成、采样等多种工具&#xff0c;可用于制作各种类型的音乐&#xff0c;如流行音乐、电子音乐、摇滚乐等。 Studio One mac软件特点 1. 直观易用的界面&…

Spring中静态代理设计模式

目录 一、为什么需要代理设计模式 二、代理设计模式 三、静态代理设计模式 3.1 存在的问题 一、为什么需要代理设计模式 在项目的开发过程中我们知道service层是整个项目中最重要的部分&#xff0c;在service中一般会有两个部分&#xff0c;一个是核心业务&#xff0c;一个是额…

DJYROS产品:基于DJYOS的国产自主割草机器人解决方案

基于都江堰泛计算操作系统的国产自主机器人操作系统即将发布…… 1、都江堰机器人操作系统命名&#xff1a;DJYROS 2、机器人算法&#xff1a;联合行业自主机器人厂家&#xff0c;构建机器人算法库。 3、机器人芯片&#xff1a;联合行业机器人AI芯片公司&#xff0c;构建专用…

电商API是何时?以什么姿态开启了它的时代?

说到API&#xff0c;非业内技术人士&#xff0c;大家似乎对它还是知之甚少。 但如果有关注这个领域&#xff0c;其实不难发现&#xff0c;国内一些所谓大厂已经在电商API接口方面做了不少动作&#xff0c;不论是对外宣称的API生态&#xff0c;还是相对低调的API市场&#xff0c…

冲刺学习-MySQL-基础

基础 数据类型 常见数据类型的属性 整型 TINYINT、SMALLINT、MEDIUMINT、INT&#xff08;INTEGER&#xff09;和 BIGINT 可选属性 M&#xff1a;表示显示宽度&#xff08;从MySQL 8.0.17开始&#xff0c;整数数据类型不推荐使用显示宽度属性&#xff09;UNSIGNED&#xff1…

hdlbits系列verilog解答(7458芯片)-10

文章目录 wire线网类型介绍一、问题描述二、verilog源码三、仿真结果 wire线网类型介绍 wire线网类型是verilog的一种数据类型&#xff0c;它是一种单向的物理连线。它可以是输入也可以是输出&#xff0c;它与reg寄存器数据类型不同&#xff0c;它不能存储数据&#xff0c;只能…

设计模式——七大原则详解

这里写目录标题 设计模式单一职责原则应用实例注意事项和细节 接口隔离原则应用实例 依赖倒转&#xff08;倒置&#xff09;原则基本介绍实例代码依赖关系传递的三种方式注意事项和细节 里氏替换原则基本介绍实例代码 开闭原则基本介绍实例代码 迪米特法则基本介绍实例代码注意…

【苍穹外卖 | 项目日记】第八天

前言&#xff1a; 昨天晚上跑完步回来宿舍都快停电了&#xff0c;就没写项目日记&#xff0c;今天补上 目录 前言&#xff1a; 今日完结任务&#xff1a; 今日收获&#xff1a; 引入百度地图接口&#xff1a; 引入spring task &#xff0c;定时处理异常订单&#xff1a; …

ssm+vue的软考系统(有报告)。Javaee项目,ssm vue前后端分离项目。

演示视频&#xff1a; ssmvue的软考系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;ssm vue前后端分离项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&#xff…

【RNA structures】RNA-seq Part2: RNA转录的重构和前沿测序技术

文章目录 RNA转录重建1 先简单介绍一下测序相关技术2 Map to Genome Methods2.1 Step1 Mapping reads to the genome2.2 Step2 Deal with spliced reads2.3 Step 3 Resolve individual transcripts and their expression levels 3 Align-de-novo approaches3.1 Step 1: Generat…

你还不会DeBug?太low了吧

编程时调试是不可缺少的&#xff0c;Unity中用于调试的方法均在Debug类中。 浅试一下 新建一个物体和脚本&#xff0c;并把脚本挂载到物体上&#xff01; using System.Collections; using System.Collections.Generic; using UnityEngine;public class DeBugTest : MonoBeh…

JavaSE入门---掌握面向对象三大特性:封装、继承和多态

文章目录 封装什么是封装&#xff1f;如何实现封装&#xff1f; 继承什么是继承&#xff1f;继承的语法父类成员访问子类访问父类的成员变量子类访问父类的成员方法 认识super关键字认识final关键字子类的构造方法super VS this在继承关系中各代码块的执行顺序是怎样的&#xf…

AAPCS:最新的ARM子程序调用规则

AAPCS是arm公司发布的ARM架构应用程序二进制&#xff08;ABI&#xff09;程序调用接口&#xff0c;该文档由多个版本&#xff0c;博主第一次ARM程序调用规则是在《ARM体系与结构编程》&#xff0c;但书中描述的是ATPCS&#xff0c;AAPCS是ATPCS的升级版。后面去ARM官网看到了AA…

冯诺依曼体结构 - 为什么要有操作系统

冯诺依曼体系结构 基础 概念认识 我们现在常见的 计算机&#xff0c;如 笔记本电脑&#xff0c;或者是不常见的 计算机&#xff0c;如服务器&#xff0c;大部分都遵循一个体系结构 -- 冯诺依曼体系结构。计算机的基本构成 就是由 冯诺依曼体系结构 来构成计算机的基本单元的。…

机械设备经营小程序商城的作用是什么

由于机械设备厂商品牌需要各地招商代理&#xff0c;因此在管理方面也需要工具进行高效管理。如今各个行业都在开展数字化转型解决行业所遇难题或通过线上销售解决传统三公里难题及品牌扩张难题、用户消费渠道少等难题&#xff0c;构建会员体系精细化管理&#xff0c;同时还需要…

轻松快速搭建一个本地的语音合成服务

前言 本文将介绍一个可以快速搭建本地语音合成的服务&#xff0c;模型和代码已全部提供&#xff0c;不需要联网运行。项目使用的是VITS模型结构&#xff0c;能够很轻松地启动服务。 安装环境 安装Pytorch。 # 安装CPU版本的Pytorch conda install pytorch torchvision torc…

【了解一下,Elastic Search的检索】

文章目录 **1.1.ES**1.1.1.elasticsearch的作用**1.1.2.ELK栈****2.索引库操作****2.1.mapping映射属性****2.2.索引库的CRUD** **3. 文档操作** **基于IDEA操作ES****索引操作****文档操作** DSL查询文档**1.1.DSL查询分类****1.2. 全文检索查询****1.3. 精准查询****1.4. 地理…