优选算法之二分查找(上)

目录

一、二分查找

1.题目链接:704. 二分查找

2.题目描述:

3.算法流程:

4.算法代码:

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

1.题目链接:34. 在排序数组中查找元素的第一个和最后一个位置

2.题目描述:

3.算法流程:

4.算法代码:

三、x的平方根

1.题目链接:69. x 的平方根 

2.题目描述:

3.解法一(暴力解法)

🌴算法思路:

🌴算法代码:

4.解法二(二分查找算法)

🌴算法思路:

🌴算法代码:

四、搜索插入位置

1.题目链接:35. 搜索插入位置

2.题目描述:

3.解法(二分查找算法)

🌴算法思路:

🌴算法代码:

五、二分模板


一、二分查找

1.题目链接:704. 二分查找

2.题目描述:

给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target  ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1


示例 1:

输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4

示例 2:

输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1

提示:

  1. 你可以假设 nums 中的所有元素是不重复的。
  2. n 将在 [1, 10000]之间。
  3. nums 的每个元素都将在 [-9999, 9999]之间。

3.算法流程:

a. 定义 left , right 指针,分别指向数组的左右区间。

b. 找到待查找区间的中间点 mid ,找到之后分三种情况讨论:

  1. arr[mid] == target 说明正好找到,返回 mid 的值;
  2. arr[mid] > target 说明 [mid, right] 这段区间都是大于 target 的,因此舍去右边区间,在左边 [left, mid -1] 的区间继续查找,即让 right = mid - 1 ,然后重复 2 过程;
  3. arr[mid] < target 说明 [left, mid] 这段区间的值都是小于 target 的,因此舍去左边区间,在右边 [mid + 1, right] 区间继续查找,即让 left = mid + 1 ,然后重复 2 过程;

c. 当 left 与 right 错开时,说明整个区间都没有这个数,返回 -1

4.算法代码:

class Solution 
{
public:int search(vector<int>& nums, int target) {// 初始化 left 与 right 指针int left = 0, 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;elseleft = mid + 1;}// 如果程序⾛到这⾥,说明没有找到⽬标值,返回 -1return -1;}
};

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

1.题目链接:34. 在排序数组中查找元素的第一个和最后一个位置

2.题目描述:

给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。

如果数组中不存在目标值 target,返回 [-1, -1]

你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。

示例 1:

输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]

示例 2:

输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]

示例 3:

输入:nums = [], target = 0
输出:[-1,-1]

3.算法流程:

        用的还是二分思想,就是根据数据的性质,在某种判断条件下将区间一分为二,然后舍去其中一个区间,然后在另一个区间内查找;为了方便叙述,我们用 x 表示该元素, resLeft 表示左边界, resRight 表示右边界。


寻找左边界思路:

🌵寻找左边界:

我们注意到以左边界划分的两个区间的特点为:

  • 左边区间 [left, resLeft - 1] 都是小于 x 的;
  • 右边区间(包括左边界) [resLeft, right] 都是大于等于 x 的;

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

  • 当我们的 mid 落在 [left, resLeft - 1] 区间的时候,也就是 arr[mid] <target 。说明 [left, mid] 都是可以舍去的,此时更新 left 到 mid + 1 的位置,继续在 [mid + 1, right] 上寻找左边界;
  • 当 mid 落在 [resLeft, right] 的区间的时候,也就是 arr[mid] >= target 。说明 [mid + 1, right] (因为 mid 可能是最终结果,不能舍去)是可以舍去的,此时更新 right 到 mid 的位置,继续在 [left, mid] 上寻找左边界;

🌵由此,就可以通过二分,来快速寻找左边界;

注意:这里找中间元素需要向下取整。因为后续移动左右指针的时候:

  • 左指针: left = mid + 1 ,是会向后移动的,因此区间是会缩小的;
  • 右指针: right = mid ,可能会原地踏步(比如:如果向上取整的话,如果剩下 1,2 两个元素, left == 1 , right == 2 , mid == 2 。更新区间之后, left,right,mid 的值没有改变,就会陷入死循环)。

因此⼀定要注意,当 right = mid 的时候,要向下取整。


寻找右边界思路:

🌴寻找右边界:

用 resRight 表示右边界;我们注意到右边界的特点为:

  • 左边区间 (包括右边界) [left, resRight] 都是小于等于 x 的;
  • 右边区间 [resRight+ 1, right] 都是大于 x 的;

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

  • 当我们的 mid 落在 [left, resRight] 区间的时候,说明 [left, mid - 1]( mid 不可以舍去,因为有可能是最终结果) 都是可以舍去的,此时更新 left 到 mid的位置;
  • 当 mid 落在 [resRight+ 1, right] 的区间的时候,说明 [mid, right] 内的元素是可以舍去的,此时更新 right 到 mid - 1 的位置;

🌴由此,就可以通过二分,来快速寻找右边界;

注意:这里找中间元素需要向上取整。因为后续移动左右指针的时候:

  • 左指针: left = mid ,可能会原地踏步(比如:如果向下取整的话,如果剩下 1,2 两个元素, left == 1, right == 2,mid == 1 。更新区间之后, left,right,mid 的值没有改变,就会陷入死循环)。
  • 右指针: right = mid - 1 ,是会向前移动的,因此区间是会缩小的;

因此一定要注意,当 right = mid 的时候,要向下取整。

4.算法代码:

class Solution
{
public:vector<int> searchRange(vector<int>& nums, int target) {// 处理边界情况if(nums.size() == 0) return {-1, -1};int begin = 0;int left = 0, right = nums.size() - 1;// 查找区间左端点while(left < right){int mid = left + (right - left) / 2;if(nums[mid] < target)left = mid + 1;elseright = mid;}// 判断是否有结果if(nums[left] != target) return {-1, -1};else begin = left;// 标记左端点// 查找区间右端点left = 0, right = nums.size() - 1;while(left < right){int mid = left + (right - left + 1) / 2;if(nums[mid] <= target)left = mid;elseright = mid - 1;}return {begin, right};}
};

三、x的平方根

1.题目链接:69. x 的平方根 

2.题目描述:

给你一个非负整数 x ,计算并返回 x 的 算术平方根 。

由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。

注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5 。

示例 1:

输入:x = 4
输出:2

示例 2:

输入:x = 8
输出:2
解释:8 的算术平方根是 2.82842..., 由于返回类型是整数,小数部分将被舍去。

3.解法一(暴力解法

🌴算法思路:

        依次枚举 [0, x] 之间的所有数 i :(这里没有必要研究是否枚举到 x / 2 还是 x / 2 + 1 。因为我们找到结果之后直接就返回了,往后的情况就不会再判断。反而研究枚举区间,既耽误时间,又可能出错)

  • 如果 i * i == x ,直接返回 x ;
  • 如果 i * i > x ,说明之前的一个数是结果,返回 i - 1 。 由于 i * i 可能超过 int 的最大值,因此使用 long long 类型。

🌴算法代码:

class Solution 
{
public:int mySqrt(int x) {// 由于两个较⼤的数相乘可能会超过 int 最⼤范围// 因此⽤ long longlong long i = 0;for(i = 0; i <= x; i++){// 如果两个数相乘正好等于 x,直接返回 iif(i * i == x)return i;// 如果第⼀次出现两个数相乘⼤于 x,说明结果是前⼀个数if(i * i > x)return i - 1;}// 为了处理oj题需要控制所有路径都有返回值return -1;}
};

4.解法二(二分查找算法

🌴算法思路:

设 x 的平方根的最终结果为 index :

分析 index 左右两侧数据的特点:

  • [0, index] 之间的元素,平方之后都是小于等于 x 的;
  • [index + 1, x] 之间的元素,平方之后都是x 的。
  • 因此可以使用二分查找算法。

🌴算法代码:

class Solution 
{
public:int mySqrt(int x) {if(x < 1) return 0;// 处理边界情况int left = 1, right = x;while(left < right){// 防溢出long long mid = left + (right - left + 1) / 2;if(mid * mid <= x)left = mid;elseright = mid - 1;}return right;}
};

四、搜索插入位置

1.题目链接:35. 搜索插入位置

2.题目描述:

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

请必须使用时间复杂度为 O(log n) 的算法。

示例 1: 

输入: nums = [1,3,5,6], target = 5
输出: 2

示例 2:

输入: nums = [1,3,5,6], target = 2
输出: 1

示例 3:

输入: nums = [1,3,5,6], target = 7
输出: 4

3.解法(二分查找算法

🌴算法思路:

a. 分析插入位置左右两侧区间上元素的特点:
设插入位置的坐标为 index ,根据插入位置的特点可以知道:

  • [left, index - 1] 内的所有元素均是小于 target 的;
  • [index, right] 内的所有元素均是大于等于 target 的。

b. 设 left 为本轮查询的左边界, right 为本轮查询的右边界。根据 mid 位置元素的信息,分析下⼀轮查询的区间:

  • 当 nums[mid] >= target 时,说明 mid 落在了 [index, right] 区间上,mid 左边包括 mid 本身,可能是最终结果,所以我们接下来查找的区间在 [left,mid] 上。因此,更新 right 到 mid 位置,继续查找。
  • 当 nums[mid] < target 时,说明 mid 落在了 [left, index - 1] 区间上,mid 右边但不包括 mid 本身,可能是最终结果,所以我们接下来查找的区间在 [mid + 1, right] 上。因此,更新 left 到 mid + 1 的位置,继续查找。

c. 直到我们的查找区间的长度变为 1 ,也就是 left == right 的时候, left 或者right 所在的位置就是我们要找的结果。

🌴算法代码:

class Solution 
{
public: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;elseright = mid;}if(nums[left] < target) return left + 1;return left;}
};

五、二分模板

在求 mid 的时候,只有 right - 1 的情况下,才会向上取整(也就是 +1 取中间数)。

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

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

相关文章

matlab2018b安装

1.可先参考这个 2.激活 按上面教程安装后&#xff0c;打开matlab 可能会出现软件激活这个界面&#xff0c;需要按如下步骤进行操作。

从理论到实践:如何用 TDengine 打造完美数据模型​

在用 TDengine 进行数据建模之前&#xff0c;我们需要回答两个关键问题&#xff1a;建模的目标用户是谁&#xff1f;他们的具体需求是什么&#xff1f;在一个典型的时序数据管理方案中&#xff0c;数据采集和数据应用是两个主要环节。如下图所示&#xff1a; 对于数据采集工程师…

Bootstrap5 Navbar多级下拉框

实现目标&#xff1a; 1、访问 Bootstrap5-navbar 2、修改dropdown为多级 <!DOCTYPE HTML> <html lang"en-US"> <head><meta charset"UTF-8"><title></title><link rel"stylesheet" href"https…

Unity DOTS中的world

Unity DOTS中的world 注册销毁逻辑自定义创建逻辑创建world创建system group插入player loopReference DOTS中&#xff0c;world是一组entity的集合。entity的ID在其自身的世界中是唯一的。每个world都拥有一个EntityManager&#xff0c;可以用它来创建、销毁和修改world中的en…

[AWS]MSK调用,报错Access denied

背景&#xff1a;首先MSK就是配置一个AWS的托管 kafka&#xff0c;创建完成之后就交给开发进行使用&#xff0c;开发通常是从代码中&#xff0c;编写AWS的access_key 和secret_key进行调用。 但是开发在进行调用的时候&#xff0c;一直报错连接失败&#xff0c;其实问题很简单&…

【机器学习】机器学习之计算学习理论--评估机器学习能够学到什么程度

引言 计算学习理论&#xff08;Computational Learning Theory&#xff0c;CLT&#xff09;是机器学习的一个分支&#xff0c;它使用数学工具来分析和理解机器学习算法的效率和可能性 计算学习理论主要关注三个核心问题&#xff1a;学习模型的表示、学习算法的效率和学习的泛化…

Matlab画不同指标的对比图

目录 一、指标名字可修改 二、模型名字可修改 三、输入数据可修改 软件用的是Matlab R2024a。 clear,clc,close all figure1figure(1); % set(figure1,Position,[300,100,800,600],Color,[1 1 1]) axes1 axes(Parent,figure1);%% Initialize data points 一、指标名字可修…

Astro 4.12 发布,新增支持服务器岛屿

近日&#xff0c;Astro 发布了最新的 4.12 版本&#xff0c;此版本包含 Server Islands&#xff08;服务器岛屿&#xff09;&#xff0c;这是 Astro 将高性能静态 HTML 和动态服务器生成的组件集成在一起的新解决方案&#xff0c;此版本还包括对分页和语法突出显示的改进。 要…

如何检查我的网站是否支持HTTPS

HTTPS是一种用于安全通信的协议&#xff0c;是HTTP的安全版本。HTTPS的主要作用在于为互联网上的数据传输提供安全性和隐私保护。通常是需要在网站安装部署SSL证书来实现网络数据加密传输&#xff0c;安全加密功能。 那么如果要检查你的网站是否支持HTTPS&#xff0c;可以看下…

C#基于SkiaSharp实现印章管理(4)

前几篇文章实现了绘制不同外形印章的功能&#xff0c;印章内部一般包含圆形、线条等形状&#xff0c;有些印章内部还有五角星&#xff0c;然后就是各种样式的文字。本文实现在印章内部绘制圆形、线条、矩形、椭圆等四种形状。   定义FigureType枚举记录印章内部形状&#xff…

数据结构——堆(C语言版)

树 树的概念&#xff1a; 树&#xff08;Tree&#xff09;是一种抽象数据结构&#xff0c;它由节点&#xff08;node&#xff09;的集合组成&#xff0c;这些节点通过边相连&#xff0c;把 节点集合按照逻辑顺序抽象成图像&#xff0c;看起来就像一个倒挂着的树&#xff0c;也…

react入门到实战-day1

这react门课我是学习b站黑马的课程&#xff0c;不是打公告哈&#xff0c;我只是过一遍&#xff0c;让自己对学过的知识有印象&#xff0c;所以笔记是有很大部分直接复制总结过来的&#xff0c;方便后面的我进行复习。如有冒犯&#xff0c;联系必删 React介绍以及创建方式 React…

基于FPGA的以太网设计(2)----以太网的硬件架构(MAC+PHY)

1、概述 以太网的电路架构一般由MAC、PHY、变压器、RJ45和传输介质组成,示意图如下所示: 需要注意的是,上图是一个简化了的模型,它描述的是两台主机之间的直接连接,但在实际应用中基本都是多台主机构成的局域网,它们之间并不直接相连,而是通过交换机Switch来进行…

JAVA开发工具IDEA如何连接操作数据库

一、下载驱动 下载地址&#xff1a;【免费】mysql-connector-j-8.2.0.jar资源-CSDN文库 二、导入驱动 鼠标右击下载到IDEA中的jar包&#xff0c;选择Add as Library选项 如图就导入成功 三、加载驱动 Class.forName("com.mysql.cj.jdbc.Driver"); 四、驱动管理…

FPGA开发在verilog中关于阻塞和非阻塞赋值的区别

一、概念 阻塞赋值&#xff1a;阻塞赋值的赋值号用“”表示&#xff0c;对应的是串行执行。 对应的电路结构往往与触发沿没有关系&#xff0c;只与输入电平的变化有关系。阻塞赋值的操作可以认为是只有一个步骤的操作&#xff0c;即计算赋值号右边的语句并更新赋值号左边的语句…

软件缺陷(Bug)、禅道

目录 软件缺陷的判定标准 软件缺陷的核心内容 构成缺陷的基本要素 缺陷报告 缺陷管理 缺陷的跟踪流程 项目管理工具--禅道 软件在使用过程中存在的任何问题&#xff08;如&#xff1a;错误、异常等&#xff09;&#xff0c;都叫软件的缺陷&#xff0c;简称bug。 软件缺…

学习记录--Bert、Albert、RoBerta

目录 Bert 1&#xff1a;输入 2&#xff1a;Bert结构 3&#xff1a;模型预训练 Albert 1&#xff1a;SOP任务 2&#xff1a;embedding因式分解 3&#xff1a;参数共享 RoBerta 参考&#xff1a; BERT原理和结构详解_bert结构-CSDN博客 [LLM] 自然语言处理 --- ALBER…

鸿蒙华为登录(以及导航页面跳转)

//登录华为登录界面以及跳转 //切记一定要写路径&#xff0c;不写路径&#xff0c;容易报错&#xff0c;还有一定要记得导一下包&#xff08;Arouter&#xff09; //接下来是鸿蒙界面导航跳转 //进行跳转 TabContent组件不支持设置通用宽度属性&#xff0c;其宽度默认撑满Tab…

在spyder中使用arcgis pro的包

历时2天终于搞定了 目标&#xff1a;在anconda中新建一个arcpyPro环境&#xff0c;配置arcgispro3.0中的arcpy 一、安装arcgispro3.0 如果安装完之后打开arcgispro3.0闪退&#xff0c;就去修改注册表&#xff08;在另一台电脑安装arcgispro遇到过&#xff09; 安装成功后可…

MySQL聚合函数(DQL)

先看一下我的表内容和数据&#xff0c;再做接下来的例子和讲解 1.聚合函数的基本语法 SELECT 聚合函数&#xff08;表中的某个字段&#xff09;FROM 表名; 2. 常见的聚合函数 举例 1.统计该企业的数量 select count(idcard) from emp; 2.统计该企业员工的平均年龄 select…