【算法】使用二分查找解决算法问题:理解二分法思想,模板讲解与例题实践

news2025/3/6 21:12:53

文章目录

  • 二分算法思想 / 性质 / 朴素模板
    • 二分查找的引入(二段性)
      • 704.二分查找
    • 模板
      • 34.在排序数组中查找元素的第一个和最后一个位置
    • 二分查找的前提条件 / 时间复杂度分析
  • 算法题
    • 69.x的平方根
    • 35.搜索插入位置
    • 852.山脉数组的峰顶索引
    • 162.寻找峰值
    • 153.寻找旋转排序数组中的最小值
    • LCR173.点名

二分算法思想 / 性质 / 朴素模板

二分查找的引入(二段性)

  1. 首先,关于二分的题,重点在于理解二分法思想,当理解后,模板自然便可写出来,变成简化思路的工具。
  2. 我们通过下面的一道题,理解二分查找算法思想(并简单了解所谓模板)

704.二分查找

在这里插入图片描述

对于该题,我们首先思考 暴力解法

  1. 很简单,遍历数组,如果找到该数则返回下标,否则返回-1
  2. 我们知道:当数组元素很多时,或每次目标值与起始位置很远时,暴力解法的时间开销是很大的

此时我们引入一个思考:

  1. 当我们在数组中随机选取一个数x:
    • 如果x<target,那么目标值一定在x后面(x前面的数就不用再看了);
    • 当x>target,我们就可以直接去前面找目标值。
  2. 此时数组就被分成了两部分,即x前面的部分和x后面的部分,我们跟随查找条件直接去到相应的区间即可。
  3. 我们可以感知到题目由x与target的关系被分成了两段,这就是二段性
  4. 当一个题目有二段性的时候,我们可以采用二分法解题。

在这里插入图片描述

代码:

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 + 1) / 2; // 更新 mid
            if(nums[mid] < target) // 当前值<目标值 : 更新左边界
                left = mid + 1;    
            else if(nums[mid] > target) // 当前值>目标值 : 更新右边界
                right = mid - 1;
            else // 找到目标值,返回下标
                return mid;
        }
        return -1; // 未找到,返回-1
    }
};

上面的代码很好理解:

  • 当左指针未超过右指针时,每次更新中间指针mid,通过nums[mid] 与 target的关系比,更新左右边界,直到找到target

模板

当我们理解了二分思想,对于一道题,重点在于分析出该题具有二段性,随后写代码就不是难事了,但这里还是简单写出朴素模板:

while(left <= right)
{
    int mid = left + (right - left + 1) / 2;
    if(...) // 根据题意左右边界的更新也有所不同
        left = mid + 1;
        // left = mid;
    else if(...)
        right = mid - 1;
        // right = mid;
    else
    	return ...;
}

这里需要注意的是mid 的更新:

  1. 平时我们有mid = (left + right) / 2写法来进行中间值的更新,这里不提倡这种写法。因为当right和left过大,这种写法会造成整形溢出

  2. 而对于mid = left + (right - left + 1) / 2mid = left + (right - left) / 2是否+1,我们分为下面的情况
    在这里插入图片描述

  3. 那么我们什么时候采用法①或采用法②

    • 当代码下面出现“-1”的时候,我们更新mid时就“+1”
    • 即当我们有了后续更新left、right边界后,通过判断是否有mid - 1,在求中间值时是否将(right - left + 1)部分改为(right - left + 1)
  4. 我们通过下面的一道题来理清二分查找的细节处理。


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

在这里插入图片描述

思路

  1. 题目给出了一个非递减顺序的数组(即要么递增要么重复),要求O(logn)的时间复杂度,这两点其实就可以想到要用二分了(
  2. 根据题目要求,我们需要找到目标值的左右端点:
    • 找左端点,数组被分成两部分:① 小于t ② 大于等于t
    • 找右端点,数组分为两部分:① 小于等于t ② 大于t
    • 这样分组可以让我们通过更新区域来找到端点
  3. 此时题目有明显的二段性,我们可以使用二分查找来解题
  4. 由于此题为了解释二分代码的细节问题,会花篇幅进行细节解释:

细节问题

  1. 首先关于循环条件:使用left < right 而不是 <=。
    在这里插入图片描述

  2. 关于求中点的操作:
    我们直接选取一个极端情况:当区间只剩下两个元素时。

    • 对于找左端点的情况
      在这里插入图片描述
      由上图我们知道,当找左端点时:应该使用①进行求中点操作。
  3. 左右区间的更新

    • 对于求左端点的情况:
      在这里插入图片描述
    • 对于求右端点的情况:
      在这里插入图片描述

代码

vector<int> searchRange(vector<int>& nums, int target) {
    // 处理边界情况
    if(nums.size() == 0)    return {-1, -1};

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

    // 2. 找右区间端点
    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, right};
}

二分查找的前提条件 / 时间复杂度分析

使用条件

  1. 二分查找算法必须在有序的序列中才能使用。

  2. 二分查找的核心思想是通过比较中间位置的元素与目标元素的大小关系,确定目标元素可能存在的区域。如果数组是无序的,那么就无法保证中间位置的元素与目标元素的大小关系。

时间复杂度

二分查找的每一次迭代中,会将查找区域划分为两个子区域,并通过比较中间位置的元素与目标元素的大小关系,确定目标元素可能存在的区域。这样,每一次迭代都能将查找区域缩小一半。

假设要查找的数组长度为n,每次迭代后查找区域的长度会减少一半,直到找到目标元素或者确定目标元素不存在。因此,最坏情况下,二分查找的迭代次数为 k,满足 n / 2^k = 1。

通过求解上述方程可以得到 k = log2(n),即二分查找的时间复杂度为 O(log n)。


算法题

69.x的平方根

在这里插入图片描述

  1. 如图将数组分为 小于等于x大于x 两段区间
  2. 根据二分法:
    • mid 的平方小于等于 x,则更新 left 为 mid
    • mid 的平方大于 x,则更新 right 为 mid - 1

思路

在这里插入图片描述

代码

int mySqrt(int x) {
    // 处理边界情况
    if(x < 1) return 0;

    long long left = 0, right = x;
    // 二分法
    while(left < right)
    {
        long long mid = left + (right - left + 1) / 2;
        if(mid * mid <= x) left = mid;
        else right = mid - 1; // 出现mid-1,上面求mid用"+1"
    }

    return (int)left;
}

35.搜索插入位置

在这里插入图片描述

思路

  1. 根据题目:排序数组、返回索引,O(logn)的算法,此时大概率可以用二分解题
    在这里插入图片描述

代码

  1. 写代码时注意几点:根据上文介绍的,由我们更新left,right的方式,while循环中写left < right
  2. 当循环结束后,有两种可能:
    • 找不到target:则target应该插入到数组外,即下标为left+1
    • 找到了:left即为待插入位置
int searchInsert(vector<int>& nums, int target) {
    // 二分法
    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;
    }
    if(nums[left] < target) return left+1; // 数组中无target,插入位置在数组外
    else return left; // 数组中有t / 无t+插入位置在数组内
}

852.山脉数组的峰顶索引

在这里插入图片描述
思路

  1. 题目看起来比较模糊,通过看示例基本可以理解,简单理解就是:数组存在一个峰值,峰值左边的数是递增的,峰值右边的数是递减的。
    在这里插入图片描述
    代码
int peakIndexInMountainArray(vector<int>& arr) {
    int left = 0, right = arr.size() - 1;
    // 峰值左侧区间 arr[i] > arr[i-1] 向右找 left = mid;
    // 峰值右侧区间 arr[i] < arr[i-1] 向左找 right = mid - 1;
    while(left < right)
    {
        int mid = left + (right - left + 1) /2;
        if(arr[mid] > arr[mid-1]) left = mid;
        else right = mid - 1;
    }
    return left; // 返回峰值索引
}

162.寻找峰值

在这里插入图片描述

思路

在这里插入图片描述

代码

int findPeakElement(vector<int>& nums) {
    int left = 0, right = nums.size() - 1;
    // 取下标i,如果有num[i] > nums[i+1] 这两个数是递减,则这两数左侧必定存在一个峰值
    // 如果nums[i] < nums[i] 则右侧一定存在峰值
    while(left < right)
    {
        int mid = left + (right - left) / 2;
        if(nums[mid] > nums[mid + 1])   right = mid;// 找左区间
        else left = mid + 1;
    }
    return left;
}

153.寻找旋转排序数组中的最小值

在这里插入图片描述

思路
在这里插入图片描述

代码

int findMin(vector<int>& nums) {
    int n = nums.size();
    int left = 0, right = n - 1;
    // 最小值的左侧区间,nums[i] < nums[n-1] 成立
    // 右侧区间,nums[i] > nuns[n-1] 成立
    // 二段性->二分法
    while(left < right)
    {
        int mid = left + (right - left) / 2;
        if(nums[mid] > nums[n-1])  left = mid + 1;
        else right = mid; 
    }  
    return nums[left];
}

LCR173.点名

在这里插入图片描述
思路

  1. 实际就是找0~n-1中缺失的一个数字
  2. 由于数组中少了一个数字,我们可以知道:
    • 在缺少的数字x左侧,满足:值==下标
    • 缺少的数字x右侧,满足:值>下标(即值==下标+1)
    • 据此得到二段性,可以使用二分法
  3. 值得一提的事,这道题解法很多,哈希、位运算、直接遍历、数学方式等,都可以尝试。

代码

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(records[mid] == mid) left = mid + 1;
        else right = mid;
    }
    // 处理边界情况:当确实的数为n的时候
    if(records[right] == right) return n;
    return right;
}

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

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

相关文章

Servlet-Filter 执行顺序测试

Servlet-Filter 执行顺序测试 对于 web.xml 文件注册过滤器这里就不多说了&#xff0c;就是谁声明的早&#xff0c;谁先被调用。因为在上面的过滤器信息最先被扫描到。 模型抽象 为了便于在实践中使用&#xff0c;结合部分底层原理&#xff0c;我们可以对 Filter 链的执行做…

【3D生成与重建】SSDNeRF:单阶段Diffusion NeRF的三维生成和重建

系列文章目录 题目&#xff1a;Single-Stage Diffusion NeRF: A Unified Approach to 3D Generation and Reconstruction 论文&#xff1a;https://arxiv.org/pdf/2304.06714.pdf 任务&#xff1a;无条件3D生成&#xff08;如从噪音中&#xff0c;生成不同的车等&#xff09;、…

PlatEMO UI 界面

&#x1f389; 博主相信&#xff1a; 有足够的积累&#xff0c;并且一直在路上&#xff0c;就有无限的可能&#xff01;&#xff01;&#xff01; &#x1f468;‍&#x1f393;个人主页&#xff1a; 青年有志的博客 &#x1f4af; Github 源码下载&#xff1a;https://github.…

【快速开发】使用SvelteKit

自我介绍 做一个简单介绍&#xff0c;酒架年近48 &#xff0c;有20多年IT工作经历&#xff0c;目前在一家500强做企业架构&#xff0e;因为工作需要&#xff0c;另外也因为兴趣涉猎比较广&#xff0c;为了自己学习建立了三个博客&#xff0c;分别是【全球IT瞭望】&#xff0c;【…

数据结构之进阶二叉树(二叉搜索树和AVL树、红黑树的实现)超详细解析,附实操图和搜索二叉树的实现过程图

绪论​ “生命有如铁砧&#xff0c;愈被敲打&#xff0c;愈能发出火花。——伽利略”&#xff1b;本章主要是数据结构 二叉树的进阶知识&#xff0c;若之前没学过二叉树建议看看这篇文章一篇掌握二叉树&#xff0c;本章的知识从浅到深的对搜索二叉树的使用进行了介绍和对其底层…

uniapp H5项目使用ucharts的Echart组件方式创建圆环

问题&#xff1a;没有报错但是图表不出来 【 调试了半天圆环图表没有不出来。是因为没有明示设置宽度与高度】 /* 请根据实际需求修改父元素尺寸&#xff0c;组件自动识别宽高 */ .charts-box { width: 100%; height: 300px; } 最终效果 先导入ucharts到项目 uniapp的项目…

hadoop集群的开启与关闭

背景 很久没完hadoopl,连怎么开启关闭都不会了qwq 1.进入安装hadoop的目录 我这里是已经进入了 2.开启集群 sbin/start-dfs.sh 3.关闭集群 sbin/stop-dfs.sh

NLP论文阅读记录 -| 对摘要评分的通用规避攻击

文章目录 前言0、论文摘要一、Introduction1.1目标问题1.2相关的尝试1.3本文贡献 二.相关工作三.本文方法3.1 问题表述3.2 对 ROUGE 和 METEOR 的白盒输入不可知攻击3.3BERTcore 上的黑盒通用触发器搜索 四 实验效果4.1数据集4.2 对比模型4.3实施细节4.4评估指标4.5 实验结果 五…

华为Harmony——ArkTs语言

文章目录 一、简单示例二、声明式UI描述创建组件无参有参数 配置属性配置事件配置子组件 三、自定义组件基本用法基本结构成员函数/变量 一、简单示例 我们以一个具体的示例来说明ArkTS的基本组成。如下图所示&#xff0c;当开发者点击按钮时&#xff0c;文本内容从“Hello Wo…

Python编写第一个APP自动化脚本,将脚本跑起来

一、前置说明 Python 使用 Appium 做 APP自动化的基本流程&#xff08;Android平台&#xff09;&#xff1a; 启动 Appium Serveradb 连接设备&#xff08;真机或模拟器&#xff09;uiautomatorviewer 连接设备&#xff0c;定位元素信息使用appium-python-client库&#xff0…

基于ssm+jsp二手车估值与销售网络平台源码和论文

随着信息化时代的到来&#xff0c;管理系统都趋向于智能化、系统化&#xff0c;二手车估值与销售网络平台也不例外&#xff0c;但目前国内仍都使用人工管理&#xff0c;市场规模越来越大&#xff0c;同时信息量也越来越庞大&#xff0c;人工管理显然已无法应对时代的变化&#…

振动试验的工装夹具(GB/T 2423.43-2008)

但当试件体积较大&#xff0c;而且形状复杂时&#xff0c;这种固定方法显然很困难&#xff0c;这时需要制作夹具&#xff0c;让试件安装在夹具上然后把夹具牢固地固定在振动台面上&#xff0c;因此实际上夹具是试件与振动台面连接的过渡体&#xff0c;其功能是将振动台的振动和…

深度学习美化图片,绝对可行,美化效果挺好 DPED

一、背景 要美化生成的图片的效果&#xff0c;找到一个 效果如下&#xff1a; 二、步骤 1、python3.6环境&#xff0c;TensorFlow 2.0.0 2、下载代码&#xff1a;https://github.com/aiff22/DPEDx 3、将要增强的照片放在以下目录中&#xff0c;没有就新建&#xff1a; dpe…

亚马逊圣诞关键词怎么选?圣诞节促销活动有哪些?——站斧浏览器

亚马逊圣诞关键词怎么选 以下是在亚马逊圣诞期间利用长尾关键词的一些建议&#xff1a; 圣诞主题关键词&#xff1a;随着节日的临近&#xff0c;与圣诞相关的关键词搜索热度将急剧上升。在产品标题、描述、关键词等位置使用与圣诞节相关的关键词&#xff0c;比如“圣诞礼物”…

强烈推荐!好玩又好用的开源工具

今天来分享 7 个好玩又好用的开源工具&#xff0c;还可以学习项目代码&#xff01; PDF Guru&#xff1a;通用型 PDF 文件处理工具AiEditor&#xff1a;面向 AI 的下一代富文本编辑器pear-rec&#xff1a;实用工具集&#xff0c;包括截图、录屏、录音、录像等Pot&#xff1a;划…

2024年软件测试工程师如何从功能测试转成自动化测试?

前言 接触了太多测试同行&#xff0c;由于多数同行之前一直做手工测试&#xff0c;现在很迫切希望做[<u>自动化测试</u>](javascript:;)&#xff0c;其中不乏工作5年以上的同行。 从事软件自动化测试已经近十年&#xff0c;接触过底层服务端、API 、Web、APP、H5…

鸿蒙开发之hdc命令行

一、简介 hdc&#xff08;HarmonyOS Device Connector&#xff09;是HarmonyOS为开发人员提供的用于调试的命令行工具&#xff0c;通过该工具可以在windows/linux/mac系统上与真实设备进行交互。 二、环境准备 hdc工具通过HarmonyOS SDK获取&#xff0c;存放于SDK的toolchai…

Ubuntu 常用命令之 ping 命令用法介绍

&#x1f4d1;Linux/Ubuntu 常用命令归类整理 ping命令是一种网络诊断工具&#xff0c;用于测试主机之间网络的连通性。它发送ICMP Echo Request消息到指定的网络主机&#xff0c;并等待接收ICMP Echo Reply。通过这种方式&#xff0c;我们可以知道两台主机之间的网络是否畅通…

【String str = new String(“hollis“) 创建了几个对象?】

✅典型解析 创建的对象数应该是1个或者2个。 首先要清楚什么是对象? Java是一种面向对象的语言&#xff0c;而Java对象在JVM中的存储也是有一定的结构的&#xff0c;在HotSpot虚机中&#xff0c;存储的形式就是oop-klass model&#xff0c;即ava对象模型。我们在Java代码中&am…

【计算机系统结构实验】实验2 流水线中的冲突实验

2.1 实验目的 加深对计算机流水线基本概念的理解&#xff1b; 理解MIPS结构如何用5段流水线来实现&#xff0c;理解各段的功能和基本操作&#xff1b; 加深对结构冲突/数据冲突/控制冲突的理解&#xff1b; 进一步理解解决数据冲突的方法&#xff0c;掌握如何应用定向技术来…