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

文章目录

  • 二分算法思想 / 性质 / 朴素模板
    • 二分查找的引入(二段性)
      • 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; // 更新 midif(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;elsereturn ...;
}

这里需要注意的是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.hqwc.cn/news/293574.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CSS-计数器 counter-reset、counter-increment、counter-reset

计数器 CSS的计数器通过在 content 上应用 counter() 或 counters()函数来显示计数的。其中计数器是由counter-reset和counter-increment 来进行操作的。 counter-increment 语法 counter-increment参数1&#xff1a;计算器名称 该标识符由不区分大小写的字母 a 到 z&#xf…

【Qt之Quick模块】5. QML基本类型及示例用法

QML格式 QML基本类型 在 QML 中&#xff0c;有以下基本类型&#xff1a; int&#xff1a;整数类型。 Rectangle {function myFunction() {// 输出 debug 信息console.log("11 " (11));}Component.onCompleted: {myFunction();} }结果&#xff1a; 2. real&…

如何使用 Helm 在 K8s 上集成 Prometheus 和 Grafana|Part 1

本系列将分成三个部分&#xff0c;您将学习如何使用 Helm 在 Kubernetes 上集成 Prometheus 和 Grafana&#xff0c;以及如何在 Grafana 上创建一个简单的控制面板。Prometheus 和 Grafana 是 Kubernetes 最受欢迎的两种开源监控工具。学习如何使用 Helm 集成这两个工具&#x…

、写入Shellcode到注册表上线

其实本质就是将shellcode写入到注册表中&#xff0c;然后读取注册表中的shellcode&#xff0c;然后创建线程去执行shellcode。 如下图: 写入注册表shellcode 这里将shellcode写入到注册表中&#xff0c;在我们需要的时候再去读取然后执行。 这里用到如下两个Windows API函…

【2023最新版】Wireshark安装保姆级教程(超详细),及简单使用Wireshark抓包

一、官网下载地址&#xff1a; [wireshark](https://www.wireshark.org/) 二、步骤&#xff1a; 根据自己情况选择安装版本&#xff0c;我这里安装Windows版本 双击下载完成的应用 这里可以选择自己安装路径&#xff0c;也可以默认 下面点击Install后&#xff0c;安装过程中会跳…

【读论文】PSFusion

【读论文】Rethinking the necessity of image fusion in high-level vision tasks: A practical infrared and visible image fusion network based on progressive semantic injection and scene fidelity 介绍解决的问题网络架构整体架构稀疏语义感知分支&#xff08; spars…

2023 下半年系统架构设计师学习进度

文章目录 复习计划&#xff1a;每周350分钟第一周&#xff08;339分钟&#xff09;第二周&#xff08;265分钟&#xff09;第三周&#xff08;171分钟&#xff09;第四周&#xff08;214分钟&#xff09;第五周&#xff08;274分钟&#xff09;第六周&#xff08;191分钟&#…

【计算机系统结构实验】实验3 Cache性能分析

3.1 实验目的 加深对Cache的基本概念及其工作原理的理解&#xff1b; 掌握降低Cache不命中率的各种方法以及这些方法对提高Cache性能的好处&#xff1b; 理解常见替换算法&#xff08;LRU与随机法&#xff09;的基本思想以及它们对Cache性能的影响。 3.2 实验平台 实验平台…

Unity自带的NavMesh寻路组件

最近看了一下Unity自带的NavMesh寻路组件&#xff0c;先说一下基本的使用&#xff1a; 首先先把AI Navgation的package包给安装上。 给场景地图添加上NavMeshSurface组件&#xff0c;然后进行烘焙&#xff0c;烘焙出对应的场景地图文件。 给移动物体添加对应的Nav MeshAgent组…

PlatEMO 源码执行的具体过程

ALGORITHM 类 Algorithm类定义在Algorithms文件夹下。在这个目录中&#xff0c;算法根据分类分成了三种&#xff1a; 多目标算法&#xff1a;Multi-objecitve optimization单目标算法&#xff1a;Single-objective optimization工具算法&#xff1a;Utility functions 工具算法…

如何使用 NFTScan NFT API 在 Base 网络上开发 Web3 应用

Base 是 Coinbase 使用 OP Stack 开发的最新以太坊第 2 层&#xff08;L2&#xff09;网络&#xff0c;用于解决以太坊等主要区块链面临的可扩展性和成本挑战。Coinbase 将其描述为“安全、低成本、对开发人员友好的以太坊 L2&#xff0c;旨在将下一个 10 亿用户带入 Web3”。B…

Linux---优先级+并发+进程调度队列

目录 一、优先级 二、并发 三、Linux2.6内核进程调度队列 一、优先级 我们发现操作系统中有很多等待队列&#xff0c;也就是说进程需要排队&#xff0c;而排队的本质就是确认优先级&#xff0c;优先级高的排在前面&#xff0c;低的排在后面 为什么要有优先级&#xff1f; 本…