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

前言

二分查找的思想是简单易懂的,但是在具体实现的时候能被一些细节给逼疯。今天学习了一下二分查找相关的知识与小细节,听取同学的推荐,参考了大神“灵茶山艾府”的教学视频。

下面就以一道算法题为例子,来写一下二分查找的方法。但这篇博客我会不局限于这道题,尽量去着笔于二分查找的算法本身。

原题描述

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 <= 105
-109 <= nums[i] <= 109
nums 是一个非递减数组
-109<= target <= 109

解答

方法一:固定套路

思想

其实这道题从有序数组O(log n)时间复杂度来看,很显然需要用到二分查找。

也就是需要用二分查找找到目标元素target出现的第一个位置和最后一个位置。

二分查找在实现的时候,根据左右边界开闭区间的不同,有三种实现方式:闭区间[l, r]、左开右闭(l, r]、开区间(l, r)

我这里就用闭区间的方式来做,但是任何方式其实都是可以的的。

对于查找target的第一个位置,其实查找的就是>= target的第一个元素,在这里将查找>= target的函数记作findNotLess()

但是在找到之后需要判断一下找到的位置是不是合法(因为如果这个数组元素都比target小,那么找到的位置是超出了数组边界的);

还需要判断找到的位置是不是target(如果数组中不存在target,那么可能找到的是例如target+1这样的比target还要大的元素)。

像下图所示的例子。
在这里插入图片描述
如果经过判断发现,target存在数组中,并且找到了第一个出现的位置,那么如何找target的最后一个的位置?

答:找最后一个target其实就需要找到> target的第一个元素的位置,然后把这个位置-1即可。

也就相当于去找>= (target+1)的位置,然后将找到的下标-1,就得到了最后一个target的位置。(因为经过刚刚的判断,target一定存在,那么找到>= (targe+1)的位置,它左边一定是最后一个target

可以发现,在找最后一个target时,用的是一种偷懒的方法来实现的,我把这种方式称作一种固定套路。

因此,在非递减顺序排列int数组中,可以在这里进行一下拓展和总结:

  1. 查找>= target的第一个位置,直接刚刚所说的findNotLess(target)方法;
  2. 查找> target的第一个位置,可以转换成查找>= target+1,也就是findNotLess(target+1)
  3. 查找< target的最后一个位置,可以转换成查找>= target的第一个位置,然后将找到的下标减一。也就是findNotLess(target)-1
  4. 查找<= target的最后一个位置,可以转换成查找> target的第一个位置再减一。二次转换成查找>= target+1的第一个位置再减一,也就是findNotLess(target+1)-1

注意:
上述查找时,在查找大于或大于等于一个数的时候,都是查找的第一个位置。
在查找小于或小于等于一个数的时候,都是查找的最后一个位置。
这是因为在非递减排序数组中,查找大于某个数的最后一个数是没意义的,一定在数组的最后面。
同样的,查找小于某个数的第一个数也是没意义的,一定是数组的开头。

代码实现

C++代码如下

class Solution {
public:vector<int> searchRange(vector<int>& nums, int target) {int n = nums.size();int start = findNotLess(nums, target);if(start == n || nums[start] != target)return {-1, -1};int end = findNotLess(nums, target+1)-1;return {start, end};}int findNotLess(vector<int>&nums, int target){int n = nums.size();int l = 0, r = n-1;while(l <= r){int mid = l + (r-l)/2;  // 防止溢出的计算方式if(nums[mid] >= target)r = mid-1;  // r+1 都是大于等于targetelsel = mid+1;  // l-1都是小于target}return r+1; // 返回的是大于等于target的第一个数的下标}
};

复杂度分析

时间复杂度

每次都将任务拆分成了之前的一半,O(logn)

空间复杂度

没有额外的空间开销,O(1)

方法二:灵活应用

思想

灵活应用的时候就是不仅仅只用方法一中的FindNotLess()函数,在查找出现的最后一个target的时候,通过更改函数中的比较方式来进行实现。
也就是下面这一部分:

if(nums[mid] >= target)r = mid-1;  // r+1 都是大于等于target
elsel = mid+1;  // l-1都是小于target

在上述代码中,这个部分是使得r+1都是>=targetl-1都是<target。用这种方式,在结束的时候r+1的位置就是第一个target(假设target存在数组中)

我们需要改成l-1都是<=targetr+1>target,这样结束的时候l-1的位置就是最后一个target。修改过后的代码见下。

代码实现

C++代码如下
其中find_left()函数就是上面的findNotLess()函数。

class Solution {
public:vector<int> searchRange(vector<int>& nums, int target) {int n = nums.size();int start = find_left(nums, target);if(start == n || nums[start] != target)return {-1, -1};int end = find_right(nums, target);return {start, end};}int find_right(vector<int> &nums, int target){int n = nums.size();int l = 0, r = n-1;while(l <= r){int mid = l + (r-l)/2;if(nums[mid] <= target)  // 注意这个地方的不同!!!l = mid+1;elser = mid-1;}return l-1;  // 返回的是<=target的最后一个数}int find_left(vector<int>&nums, int target){int n = nums.size();int l = 0, r = n-1;while(l <= r){int mid = l + (r-l)/2;if(nums[mid] >= target)r = mid-1;  // r+1 都是大于等于targetelsel = mid+1;  // l-1都是小于target}return r+1; // 返回的是大于等于target的第一个数的下标}
};

复杂度分析

时间复杂度

O(logn)

空间复杂度

O(1)

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

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

相关文章

Peaks:每周至少要进行一次用户访谈?

名字&#xff1a;Peaks 开发者 / 团队&#xff1a;Vogelhaus Apps GmbH 平台&#xff1a;iOS、watchOS 请简要介绍下这款产品 每个人生活的节奏都有一个内置的生理时钟&#xff0c;这就是所谓的昼夜节律。它不仅控制着我们何时感到疲倦或者精力旺盛&#xff0c;更加深远的意义在…

探索314协议代币合约开发:解析AVE热搜上币与项目推广

在加密货币领域&#xff0c;新兴的项目和协议不断涌现&#xff0c;而314协议代币合约开发正是其中的一种创新。本文将深入探讨314协议代币合约开发的意义和关键性&#xff0c;以及如何利用AVE热搜上币与项目推广来推动其发展。 1. 314协议代币合约开发的意义 创新性&#xff1…

杨辉三角形(蓝桥杯,acwing)

题目描述&#xff1a; 下面的图形是著名的杨辉三角形&#xff1a; 如果我们按从上到下、从左到右的顺序把所有数排成一列&#xff0c;可以得到如下数列&#xff1a; 1, 1, 1, 1, 2, 1, 1, 3, 3, 1, 1, 4, 6, 4, 1, ... 给定一个正整数 N&#xff0c;请你输出数列中第一次出现…

保姆级教程带你实现HarmonyOS手语猜一猜元服务(一)

&#x1f680;前言 最近HarmonyOS NEXT大火&#xff0c;这个纯血鸿蒙吸引力了大家的关注。虽然现在还没面向个人开发者开放&#xff0c;但我们可以基于最新的API9及开发工具来尝试开发鸿蒙新的应用形态——元服务。来体验下未来在HarmonyOS NEXT上实现的应用开发。 HarmonyOS…

算法学习 | day38/60 零钱兑换/组合总和IV

今天主要是完全背包的一个专题&#xff0c;完全背包相对于 01 背包&#xff0c;主要区别在于物品可以取出无限次&#xff0c;其他关于 dp 的状态定义和状态转移都是一样的&#xff0c;体现在代码上&#xff0c;最重要的是遍历的一个先后顺序以及遍历开始的位置。 一、题目打卡…

Canal的使用场景!!!

1、保持redis和mysql连接的一致性&#xff1a;通常使用延迟双删功能&#xff08;具有弊端&#xff09; 解决方案&#xff1a;可以使用canal监听数据库的变化&#xff08;删改&#xff09;&#xff0c;一旦出现此类操作&#xff0c;立即删除redis中的对应数据&#xff0c;直至下…

从0到1实现RPC | 08 异常码和异常超时

定义RpcException RpcException继承RuntimeException&#xff0c;定义特定错误码&#xff0c;专用于远程过程调用过程中产生的异常。 X: 技术类异常Y: 业务类异常Z: 未知异常 在使用时&#xff0c;使用rpc专用的异常错误码&#xff0c;方便定位排查。 超时异常重试 在发生网…

【汇编语言实战】统计个数(创新版)

内存中有10个分布在0至100内的正整数&#xff0c; 求小于60的数的个数num1&#xff0c;大于或等于60且小于80的数的个数num2&#xff0c;大于或等于80且小于100的数的个数num3 C语言描述该程序流程&#xff1a; #include <stdio.h> int main() {int a[]{1, 20, 95, 32,…

基于Java+SpringBoot+Vue游戏账号估价交易平台(源码+文档+部署+讲解)

一.系统概述 系统根据现有的管理模块进行开发和扩展&#xff0c;采用面向对象的开发的思想和结构化的开发方法对游戏账号估价交易的现状进行系统调查。采用结构化的分析设计&#xff0c;该方法要求结合一定的图表&#xff0c;在模块化的基础上进行系统的开发工作。在设计中采用…

基于SSM的课程进度管理系统的设计与实现

摘要&#xff1a; 在信息技术飞速发展的当下&#xff0c; 借助互联网平台以及功能性系统的支持&#xff0c;人们获取信息以及信息整合的通道越来越多元化。系统和平台帮助人们实现了信息的共享&#xff0c;同时帮助人们更加直观的看到成果和过程的变动。以课程进度管理为例&…

服务器数据恢复—ext3文件系统下raid5数据恢复案例

服务器数据恢复环境&故障情况&#xff1a; 某企业光纤存储上有一组由16块硬盘组建的raid5阵列。管理员发现该光纤存储上的卷无法挂载&#xff0c;经过检查发现raid5阵列中有2块硬盘离线&#xff0c;于是联系我们数据恢复中心要求数据恢复工程师到现场恢复服务器存储上的数据…

Canal--->准备MySql主数据库---->安装canal

一、安装主数据库 1.在服务器新建文件夹 mysql/data&#xff0c;新建文件 mysql/conf.d/my.cnf 其中my.cnf 内容如下 [mysqld] log_timestampsSYSTEM default-time-zone8:00 server-id1 log-binmysql-bin binlog-do-db mall # 要监听的库 binlog_formatROW2.启动数据库 do…

数据交换格式

一、什么是数据交换格式 在计算机的不同程序之间&#xff0c;或者不同的编程语言之间进行交换数据&#xff0c;也需要一种大家都能听得懂得‘语言’&#xff0c;这就是数据交换格式&#xff0c;它通过文本以特定的形式来进行描述数据。 二、常用的几种数据交换格式 客户端常…

C++ //练习 11.32 使用上一题定义的multimap编写一个程序,按字典打印作者列表和他们的作品。

C Primer&#xff08;第5版&#xff09; 练习 11.32 练习 11.32 使用上一题定义的multimap编写一个程序&#xff0c;按字典打印作者列表和他们的作品。 环境&#xff1a;Linux Ubuntu&#xff08;云服务器&#xff09; 工具&#xff1a;vim 代码块 /**********************…

Vue2 —— 学习(五)

一、生命周期 &#xff08;一&#xff09;引入案例 我们想让一行文字按一定频率逐渐变得透明 1. Vue 实例外写法 函数写在 Vue 实例外面也能实现但是不推荐 <body><div id"root"><h2 :style"{opacity}">欢迎学习Vue</h2><…

大型连锁企业异地组网稳定性提升指南

随着时代的发展&#xff0c;连锁企业在网络方面面临着越来越多的挑战。这些企业在不同的地理位置设有分支机构和门店&#xff0c;可能是同城也可能是异地&#xff0c;因此需要确保各个地点之间的网络连接稳定可靠。然而&#xff0c;由于不同地区网络基础设施、延迟和带宽等方面…

代码随想录:栈与队列4-6

20.有效的括号 题目 给定一个只包括 (&#xff0c;)&#xff0c;{&#xff0c;}&#xff0c;[&#xff0c;] 的字符串 s &#xff0c;判断字符串是否有效。 有效字符串需满足&#xff1a; 左括号必须用相同类型的右括号闭合。左括号必须以正确的顺序闭合。每个右括号都有一…

打卡--MySQL8.0 四 (索引及执行计划)

一、索引的创建与使用 1、索引的分类 MySQL的索引包括普通索引、唯一性索引、全文索引、单列索引、多列索引和空间索引等。 &#xff08;1&#xff09;从 功能逻辑 上说&#xff0c;索引主要有 4 种&#xff0c;分别是普通索引、唯一索引、主键索引、全文索引。 &#xff08;…

【测试开发学习历程】python迭代、可迭代对象、迭代器、生成器

1 迭代Iteration 迭代Iteration&#xff1a;所谓迭代就是重复运行一段代码语句块的能力&#xff0c;就好比在一个容器中进行一层一层遍历数据&#xff0c;在应用过程中for循环最为突出。迭代就是从某个容器对象中逐个地读取元素&#xff0c;直到容器中没有元素为止。迭代迭代&…

【R基础】一组数据计算均值、方差与标准差方法及意义

【R基础】一组数据计算均值、方差与标准差方法及意义 均值、方差与标准差是用来描述数据分布情况 均值&#xff1a;用来衡量一组数据整体情况。 数据离散程度度量标准&#xff1a; 方差&#xff08;均方&#xff0c;s^2&#xff0c;总体参数&#xff0c;离均差平方和&#…