【十三】【算法分析与设计】二分查找(1)

704. 二分查找

给定一个 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]之间。

首先我们定义区间[left,right],保证[0,left-1]区间里面的数一定小于target[right+1,n-1]区间的数一定大于target

也就是规定left左边的数全都小于targetright右边的数全都大于target

然后一直维护这个定义,直到[left,right]区间只有一个长度单位,或者right+1==left的时候。

如果[left,right]区间只有一个长度单位的时候,此时判断这个数是否等于target即可。

如果right+1==left,说明找不到等于target的数。

为什么会出现right+1==left的情况?这种情况只可能出现mid==left或者mid==right的时候。也就是区间长度为2left+1==right的时候。

此时mid==left,更新right=mid-1就可以发生right+1=left

所以我们维护的一直是leftright的含义,left的含义是[0,left-1]区间的数全部小于targetright的含义是[right+1,n-1]区间的数全部大于target

 
class Solution {
public:int search(vector<int>& nums, int target) {// 维护区间[left,right]可能存在目标元素[0,left][right+1,n-1]一定不存在目标元素// 使得区间收敛于一个元素,然后只需要判断这一个元素是否等于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 if (nums[mid] > target) {right = mid - 1;} else {return mid;}}if (left == right && nums[left] == target)return left;elsereturn -1;}
};

这段代码定义了一个名为 Solution 的类,其中包含一个名为 search 的成员函数。这个函数的目的是在一个有序的整数数组 nums 中查找目标值 target 的索引。

int search(vector<int>& nums, int target) {

定义了 search 函数,它接受一个整数向量 nums 和一个整数 target 作为参数,并返回一个整数,表示 targetnums 中的索引,如果 target 不存在于 nums 中,则返回 -1

int left = 0, right = nums.size() - 1;

初始化两个指针 leftright,分别指向数组的起始索引和结束索引。

while (left < right) {

使用二分查找,当 left 小于 right 时,执行循环。

int mid = left + (right - left) / 2;

计算中间索引 mid

if (nums[mid] < target) {

如果中间元素的值小于 target,说明 target 在中间元素的右侧。

left = mid + 1;

left 指针移动到 mid + 1

} else if (nums[mid] > target) {

如果中间元素的值大于 target,说明 target 在中间元素的左侧。

right = mid - 1;

right 指针移动到 mid - 1

} else {return mid;}}

如果中间元素的值等于 target,返回中间索引 mid

if (left == right && nums[left] == target)return left;

最后,当 leftright 重合时,检查 left 指向的元素是否等于 target,如果是,则返回 left

else return -1;}

如果 left 指向的元素不等于 target,返回 -1

时间复杂度和空间复杂度分析

时间复杂度:O(log n),其中 n 是数组 nums 的长度。二分查找的时间复杂度为对数级别。

空间复杂度:O(1),代码中没有使用额外的存储空间,只使用了有限的几个变量。

上面的这种解法不是特别的好,因为我们并不是把nums数组分成两个部分,而是分成了三个部分,[0,x-1][x][x+1,n-1],[0,x-1]全都是小于target的数,x是等于target的数,[x+1,n-1]全都是大于target的数。但是并不是每一个nums都可以还分成这三个部分,有可能并不存在x的部分。

此时我们希望的是left和right都在x这个位置相遇,规定[0,left-1]全都是小于target的数,规定[right+1,n-1]都是大于target的数。当x不存在的时候,就会出现right+1==left的情况。

如果我们可以将nums数组精准的分成两个部分,就一定可以让left和right一定在某个位置相遇。

因此我们可以将nums划分成这样的两个部分,[0,x][x+1,n-1],[0,x]是小于等于target的数,[x+1,n-1]是大于target的数。我们希望left和right趋近x这个位置,所以规定[0,left]全都是小于等于target的数,[right+1,n-1]全都是大于target的数。

 
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;if (nums[mid] <= target) {left = mid;} else if (nums[mid] > target) {right = mid - 1;}}if (nums[left] == target)return left;elsereturn -1;}
};

这段代码有一个小细节,就是mid的取值,我们可以写成mid=left+(right-left)/2,也可以写成mid=left+(right-left+1)/2的形式。区别就是当left+1==right的时候,前者算出来的mid等于left,后者算出来的mid等于right

也可以写成mid=(left+right)/2的形式,这种形式有可能发生溢出,left+right有可能是一个很大的数,然后发生溢出。但是mid=left+(right-left)/2或者mid=left+(right-left+1)/2都不会发生移除,因为这是加法减法的运算。

这个证明也很简单,当left+1==right的时候,带入mid=left+(right-left)/2中得到mid=left+1/2=left,如果mid=left+(right-left+1)/2,此时mid=left+2/2=right

上述代码不可以写成mid=left+(right-left)/2的形式,因为如果left+1==right的时候,mid==left,left==mid会发生死循环。

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

给你一个按照非递减顺序排列的整数数组 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]

提示:

  • 0 <= nums.length <= 10(5)

  • -10(9) <= nums[i] <= 10(9)

  • nums 是一个非递减数组

  • -10(9) <= target <= 10(9)

left=mid+1,right=mid,此时可能出现right+1=left的情况吗?如果要出现这种情况,left+1==right,并且mid==right,运行left=mid+1就会发生这种情况。但是我们维护的意义是,[0,left-1]全都是小于target的数,[right,n-1]全都是大于等于target的数,此时如果出现了right+1==left,那么right这个数即小于target又大于等于target,显然不可能。所以不可能发生这种情况。

第二个while循环同理。

寻找第一个出现的target数的解题思路就是维护一个区间意义,[0,left-1]全都是小于target的数,[right,n-1]全都是大于等于target的数。

最后[left,right]区间缩小到一个长度单位,判断这个数是否等于target即可。

寻找第最后一个出现的target数的解题思路就是维护一个区间意义,[0,left]全都是小于等于target的数,[right+1,n-1]全都是大于target的数。

最后[left,right]区间缩小到一个长度单位,判断这个数是否等于target即可。

我们的最终目的就是想办法让我们的目标值趋近于一个区间长度。然后维护[left,right]区间外面的意义。

 
class Solution {
public:vector<int> searchRange(vector<int>& nums, int target) {if (nums.empty())return {-1, -1};int n = nums.size();if (target < nums[0] || target > nums[n - 1])return {-1, -1};//[left,right]维护一个区间,[0,left-1]一定小于target,[right+1,n-1]大于等于target// 我们希望区间收敛于一个元素int left = 0, right = n - 1;int begin = -1;while (left < right) {int mid = left + (right - left) / 2;if (nums[mid] < target) {left = mid + 1;} else if (nums[mid] >= target) {right = mid;}}if (nums[left] != target)return {-1, -1};elsebegin = left;left = 0, right = n - 1;//[left,right]维护一个区间,[0,left]一定小于等于target,[right+1,n-1]大于target// 我们希望区间收敛于一个元素while (left < right) {int mid = left + (right - left + 1) / 2;if (nums[mid] <= target) {left = mid;} else if (nums[mid] > target) {right = mid - 1;}}return {begin, right};}
};

这段代码定义了一个名为 Solution 的类,其中包含一个名为 searchRange 的成员函数。这个函数的目的是在一个有序的整数数组 nums 中查找目标值 target 的起始和结束位置。

vector<int> searchRange(vector<int>& nums, int target) {

定义了 searchRange 函数,它接受一个整数向量 nums 和一个整数 target 作为参数,并返回一个整数向量,包含 targetnums 中的起始和结束位置。

if (nums.empty())return {-1, -1};

如果 nums 为空,返回一个包含两个 -1 的向量,表示 target 不存在于 nums 中。

int n = nums.size();

获取数组 nums 的长度。

if (target < nums[0] || target > nums[n - 1])return {-1, -1};

如果 target 小于数组的第一个元素或者大于数组的最后一个元素,则返回一个包含两个 -1 的向量,表示 target 不存在于 nums 中。

接下来,代码使用两次二分查找,第一次查找 target 的起始位置,第二次查找 target 的结束位置。

int left = 0, right = n - 1;int begin = -1;

初始化两个指针 leftright,分别指向数组的起始索引和结束索引。初始化变量 begin-1,用于存储 target 的起始位置。

第一次二分查找:查找起始位置

while (left < right) {int mid = left + (right - left) / 2;

if (nums[mid] < target) { left = mid + 1;}

else if (nums[mid] >= target) { right = mid;}}

这个循环用来找到 target 的起始位置。如果 mid 处的值小于 target,则 target 必定在 mid 右侧;如果 mid 处的值大于或等于 target,则可能是起始位置,或者 targetmid 左侧。

if (nums[left] != target)return {-1, -1};else begin = left;

循环结束后,检查 left 指向的元素是否等于 target。如果不是,返回一个包含两个 -1 的向量。如果是,将 begin 设置为 left

第二次二分查找:查找结束位置

left = 0, right = n - 1;

重新初始化 leftright 指针。

while (left < right) {int mid = left + (right - left + 1) / 2;

if (nums[mid] <= target) { left = mid;}

else if (nums[mid] > target) { right = mid - 1;}}

这个循环用来找到 target 的结束位置。与查找起始位置的循环类似,但是当 mid 处的值小于或等于 target 时,mid 可能是结束位置,或者 targetmid 右侧,因此更新 left 指针。

return {begin, right};

返回一个向量,包含 target 的起始位置和结束位置。

时间复杂度和空间复杂度分析

时间复杂度:O(log n),其中 n 是数组 nums 的长度。该算法使用了两次二分查找,每次查找的时间复杂度为 O(log n)。

空间复杂度:O(1),代码中没有使用额外的存储空间,只使用了有限的几个变量。

35. 搜索插入位置

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

请必须使用时间复杂度为 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

提示:

  • 1 <= nums.length <= 10(4)

  • -10(4) <= nums[i] <= 10(4)

  • nums无重复元素 升序 排列数组

  • -10(4) <= target <= 10(4)

nums整个数组分成两个部分。[0,x][x+1,n-1]两部分,[0,x]部分全都是小于等于target的数,[x+1,n-1]全都是大于target的数。我们希望找到x这个位置的数字,所以我们希望leftright相遇的地点在x这个位置,此时规定,[0,left]全都是小于等于target的数,[right+1,n-1]全都是大于target的数。不断的维护这个意义,直到区间长度为1为止。因为我们确确实实可以划分出这两个部分,[0,x][x+1,n-1],所以最后一定可以到达区间长度为1的时候。

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

这段代码定义了一个名为 Solution 的类,其中包含一个名为 searchInsert 的成员函数。这个函数的目的是在一个有序的整数数组 nums 中找到目标值 target 应该被插入的位置,使得数组仍然有序。

int searchInsert(vector<int>& nums, int target) {

定义了 searchInsert 函数,它接受一个整数向量 nums 和一个整数 target 作为参数,并返回一个整数,表示 target 应该插入的索引位置。

if (nums.empty())return 0;

如果 nums 为空,则 target 应该插入在索引 0 的位置。

int n = nums.size();

获取数组 nums 的长度。

if (target < nums[0])return 0;

if (target > nums[n - 1])return n;

如果 target 小于数组的第一个元素,则应该插入在索引 0 的位置。如果 target 大于数组的最后一个元素,则应该插入在数组末尾,即索引 n 的位置。

接下来,代码使用二分查找来确定 target 的位置。

int left = 0, right = n - 1;

初始化两个指针 leftright,分别指向数组的起始索引和结束索引。

二分查找

while (left < right) {int mid = left + (right - left + 1) / 2;

计算中间索引 mid。这里加 1 是为了向上取整,防止在 leftright 相邻时进入死循环。

if (nums[mid] <= target) { left = mid;} else { right = mid - 1;}}

如果 mid 处的值小于或等于 target,则 target 应该在 midmid 右侧;如果 mid 处的值大于 target,则 target 应该在 mid 左侧。

if (nums[left] == target)return left;elsereturn left + 1;}

循环结束后,检查 left 指向的元素是否等于 target。如果是,则返回 left 作为插入位置。如果不是,说明 target 应该被插入在 left 指向的元素的右侧,即返回 left + 1 作为插入位置。

时间复杂度和空间复杂度分析

时间复杂度:O(log n),其中 n 是数组 nums 的长度。该算法使用了二分查找,时间复杂度为 O(log n)。

空间复杂度:O(1),代码中没有使用额外的存储空间,只使用了有限的几个变量。

结尾

最后,感谢您阅读我的文章,希望这些内容能够对您有所启发和帮助。如果您有任何问题或想要分享您的观点,请随时在评论区留言。

同时,不要忘记订阅我的博客以获取更多有趣的内容。在未来的文章中,我将继续探讨这个话题的不同方面,为您呈现更多深度和见解。

谢谢您的支持,期待与您在下一篇文章中再次相遇!

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

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

相关文章

win10笔记本在显示设置中不慎将主显示器禁用掉导致开机黑屏的解决方案

因为笔记本电脑的显示扩展接口有问题&#xff0c;所以在电脑开机之后&#xff0c;会误识别出几个不存在的扩展屏幕&#xff0c;所以我就想从显示设置中将这几个误识别出来的扩展屏幕禁用掉&#xff08;不然鼠标总是移动到主屏幕边界之外的地方&#xff09;&#xff0c;在显示设…

2024年腾讯云GPU服务器价格表_1小时费用_一个月价格和一年优惠

腾讯云GPU服务器怎么收费&#xff1f;GPU服务器1小时多少钱&#xff1f;一个月收费价格表和一年费用标准&#xff0c;腾讯云百科txybk.com分享腾讯云GPU服务器GPU计算型GN10Xp、GPU服务器GN7、GPU渲染型 GN7vw等GPU实例费用价格&#xff0c;以及NVIDIA Tesla T4 GPU卡和V100详细…

【SZU计算机网络实验】实现流式视频传输

前言 一百年没有更新博客了&#xff0c;都怪开学一堆杂活&#xff08;x 那就顺手把实验报告转到这边吧owo 本实验为SZU原创实验&#xff0c;实验开发团队的老师和助教们都很有耐心。。大赞&#xff0c;环境没配好去群里问是秒回的 相关资料&#xff1a; 实验文档&#xff…

k8s详细教程

Kubernetes详细教程 1. Kubernetes介绍 1.1 应用部署方式演变 在部署应用程序的方式上&#xff0c;主要经历了三个时代&#xff1a; 传统部署&#xff1a;互联网早期&#xff0c;会直接将应用程序部署在物理机上 优点&#xff1a;简单&#xff0c;不需要其它技术的参与 缺点…

JavaScript高级(十八)---进程和线程,宏任务和微任务

进程和线程 进程&#xff08;process&#xff09;&#xff1a;计算机已经运行的程序&#xff0c;是操作系统管理程序的一种方式&#xff0c;我们可以认为&#xff0c;启动一个应用程序&#xff0c;就会默认启动一个进程&#xff08;也可能是多个进程&#xff09;。 线程&…

行业模板|DataEase制造行业大屏模板推荐

DataEase开源数据可视化分析平台于2022年6月发布模板市场&#xff08;https://templates-de.fit2cloud.com&#xff09;&#xff0c;并于2024年1月新增适用于DataEase v2版本的模板分类。模板市场旨在为DataEase用户提供专业、美观、拿来即用的大屏模板&#xff0c;方便用户根据…

智能合约 之 ERC-721

ERC-721&#xff08;Non-Fungible Token&#xff0c;NFT&#xff09;标准 ERC-721是以太坊区块链上的一种代币标准&#xff0c;它定义了一种非同质化代币&#xff08;Non-Fungible Token&#xff0c;NFT&#xff09;的标准。NFT是一种加密数字资产&#xff0c;每个代币都具有独…

【计算机网络_网络层】IP协议

文章目录 1. IP的基本概念1.1 什么是IP协议1.2 为什么要有IP协议 2. IP的协议格式3. 网段划分&#xff08;重要&#xff09;3.1 为什么要进行网段划分3.2 网段划分的规则3.2.1 古老的划分方案3.2.2 现代的划分方案 4. 特殊的IP地址5. 解决IP地址的数量限制问题6. 私有IP和公网I…

深入浅出Reactor和Proactor模式

Reactor模式和Proactor模式是两种常见的设计模式&#xff0c;用于处理事件驱动的并发编程。它们在处理IO操作时有着不同的工作方式和特点。 对于到来的IO事件&#xff08;或是其他的信号/定时事件&#xff09;&#xff0c;又有两种事件处理模式&#xff1a; Reactor模式&…

HarmonyOS NEXT应用开发之元素超出List区域

介绍 本示例介绍在List组件内实现子组件超出容器边缘的布局样式的实现方法。 List组件clip属性默认为true&#xff0c;超出容器边缘的子组件会按照List的布局范围被裁剪。为此&#xff0c;可以在List组件内部添加一个占位的ListItem&#xff0c;以达到预期的布局效果。List占…

【项目实践Day06】异步请求与同步请求+Ajax+微信小程序上实现发送异步请求

什么是同步和异步 同步 在主线程上排队执行的任务&#xff0c;只有前一个任务执行完毕&#xff0c;才能继续执行下一个任务。也就是一旦调用开始&#xff0c;就必须等待其返回结果&#xff0c;程序的执行顺序和任务排列顺序一致。客户端必须等待服务器端的响应。在等待的期间客…

HTML静态网页成品作业(HTML+CSS)——宠物狗店网页(1个页面)

&#x1f389;不定期分享源码&#xff0c;关注不丢失哦 文章目录 一、作品介绍二、作品演示三、代码目录四、网站代码HTML部分代码 五、源码获取 一、作品介绍 &#x1f3f7;️本套采用HTMLCSS&#xff0c;未使用Javacsript代码&#xff0c;共有1个页面。 二、作品演示 三、代…

NVIDIA NIM 提供优化的推理微服务以大规模部署 AI 模型

NVIDIA NIM 提供优化的推理微服务以大规模部署 AI 模型 生成式人工智能的采用率显着上升。 在 2022 年 OpenAI ChatGPT 推出的推动下&#xff0c;这项新技术在几个月内就积累了超过 1 亿用户&#xff0c;并推动了几乎所有行业的开发活动激增。 到 2023 年&#xff0c;开发人员…

Covalent Network(CQT)借助最大规模的历史与实时 Web3 数据集,推动人工智能的发展

人工智能在众多领域中增强了区块链的实用性&#xff0c;反之亦然&#xff0c;区块链确保了 AI 模型所使用的数据的来源和质量。人工智能带来的生产力提升&#xff0c;将与区块链系统固有的安全性和透明度融合。 Covalent Network&#xff08;CQT&#xff09;正位于这两项互补技…

HarmonyOS NEXT应用开发之Navigation实现多设备适配案例

介绍 在应用开发时&#xff0c;一个应用需要适配多终端的设备&#xff0c;使用Navigation的mode属性来实现一套代码&#xff0c;多终端适配。 效果图预览 使用说明 将程序运行在折叠屏手机或者平板上观看适配效果。 实现思路 本例涉及的关键特性和实现方案如下&#xff1a…

backtrader回测股票:突破20日均线买入,跌破20日均线卖出

数据源&#xff1a;akshare 回测工具&#xff1a;backtrader 策略&#xff1a;突破20日均线买入&#xff0c;跌破20日均线卖出 代码&#xff1a; from datetime import datetime import backtrader as bt #1.9.78.123 import matplotlib.pyplot as plt #3.8.3 import aks…

数据库只追求性能是不够的!

那些成功的数据库公司没有一家是通过性能比竞争对手更快而成功的。 作者&#xff1a;JORDAN TIGANI&#xff0c;DuckDB 公司 MotherDuck 联合创始人&CEO 本文和封面来源&#xff1a;https://motherduck.com/&#xff0c;爱可生开源社区翻译。 本文约 4500 字&#xff0c;预…

论文阅读之AN IMAGE IS WORTH 16X16 WORDS: TRANSFORMERS FOR IMAGE RECOGNITION AT SCALE

文章目录 原文链接主要内容模型图技术细节实验结果 原文链接 AN IMAGE IS WORTH 16X16 WORDS: TRANSFORMERS FOR IMAGE RECOGNITION AT SCALE 主要内容 这篇文章的主要内容是介绍了一种新的计算机视觉模型——Vision Transformer&#xff08;ViT&#xff09;&#xff0c;这是…

自然语言处理学习总结

目录 1、词表示 2、语言模型&#xff08;LM&#xff09; 3、常用学习网址 自然语言处理 1、词表示 词表示&#xff1a;自然语言中最基本的语言单位表示成机器理解的方式 方式一&#xff1a;词与词之间的相似度 方式二&#xff1a;词与词之间的关系 词义的表示方法&…

云手机在海外电商中的应用优势

随着海外市场的不断拓展&#xff0c;电商行业对于高效、安全的工具需求日益增长。在这一背景下&#xff0c;云手机作为一种新型服务&#xff0c;为海外电商提供了强大的支持和便利。云手机对传统物理手机起到了非常好的延展和补充作用&#xff0c;拓展了更广泛的应用场景&#…