算法之二分查找优化:leetcode34:在排序数组中查找元素的第一个和最后一个位置

题干

在这里插入图片描述

分析

问题背景

给定一个已排序的数组,目标是找到一个给定的目标值 target 在数组中的 第一个位置最后一个位置。如果目标值不存在,返回 [-1, -1]。
由于题干要求的时间复杂度是 O(log n),并且数组是有序的,考虑使用二分法,接下来简单介绍二分法的思想:

二分查找是一种分治算法,它通过每次将搜索范围减少一半,快速查找目标值。在已排序的数组中,二分查找特别高效,时间复杂度是 O(log n)
如果你不了解二分法,推荐观看代码随想录的介绍视频
点击链接: 二分法

但是,这道题目要找的是 第一个位置 和 最后一个位置,因此我们不能只做一个普通的二分查找,而需要变种的二分查找来分别找出左边界(第一个位置)和 右边界(最后一个位置)。
找到一个符合条件(即nums[middle]==target)的时间复杂度为O(log n),分别找两个边界的时间复杂度约为2O(log n),依旧是O(log n)级别的。

分析题目

在普通的二分法查找中,我们通过left,right,middle指针,把数组逐渐拆分成两个部分,直至定位到符合条件(即nums[middle]==target)的下标。
但这是针对数组的的内容不重复的情况。
本题干中,数组的内容是有重复内容的,我们要算的结果,也是它的下标范围,因此当我们重新回看最经典的二分法代码:

class Solution {public int search(int[] nums, int target) {// 避免当 target 小于nums[0] nums[nums.length - 1]时多次循环运算if (target < nums[0] || target > nums[nums.length - 1]) {return -1;}int left = 0, right = nums.length - 1;while (left <= right) {int mid = left + (right - left)/2;if (nums[mid] == target) {return mid;}else if (nums[mid] < target) {left = mid + 1;}else { // nums[mid] > targetright = mid - 1;}}// 未找到目标值return -1;}
}

这是一个左闭右闭的区间,在这个区间里,每次用nums[mid]与target进行对比,再去缩小区间,在进行比较的时候,拆分成了3种情况
第一种nums[mid]==target,说明内容比对成功,是相等的,则直接返回。
第二种nums[mid]<target,说明target在middle的右边,因此要把范围缩减到右半部分,left=mid+1,继续进行循环与比较。
第三种nums[mid]>target,说明target在middle的左边,因此要把范围缩减到左半部分,right=mid-1,继续进行循环与比较。
直到不符合left<=right的条件,跳出循环。
若中间就比对成功,则直接返回下标,一直没比对成功,证明数组中没有目标值,返回-1。

而在本题中,情况略有些不同
当我们依旧使用二分法去查找目标值时,如果nums[mid]==target并不能直接返回下标,跳出循环。因为数组中可能有2个或者更多的值,我们要查找的是一个范围,而我们比对成功的那个下标可能只是这个范围中的其中一个,因此我们要从这个点继续出发,向左右两边扩展,找到它的左边界和它的右边界。

但如果我们直接使用while循环从匹配成功的nums[mid]出发,向左右寻找它的左右边界,便不符合O(log n)的时间复杂度了。
因此我们选择分别使用二分法寻找它的左右边界,而不是先定位到一点再向左右扩展。

代码思路

我们要考虑,题干具有哪些可能的情况。
情况一

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

这是正常情况,target=8在数组中有相等的值,返回下标范围3,4

情况二

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

数组中没有匹配的值,但6在数组的nums[0]nums[nums.length-1]之间

情况三

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

target在数组的范围之外

因此我们的代码,在最后进行结果的返回时,要考虑周全三种情况。
那么接下来,我们开始分别寻找leftBorderrightBorder

代码编写

leftBorder:寻找左边界

在经典二分法查找目标值时,我们分为了3种情况

  • nums[middle]>target
  • nums[middle]<target
  • nums[middle]==target

针对这三种情况,决定二分法选择左半区间还是右半区间还是直接返回。
而在本题目中,我们将进行改变,只分为2种情况

  • nums[middle]>=target
  • nums[middle]<target

之所以这样分,是因为我们在寻找左边界的时候,如果nums[middle]>target时,表示我们想要找的内容在左边部分,这毋庸置疑,因此我们要缩减区间的范围。而若nums[middle]==target时,我们也不能够停下,因为它的左边可能还有匹配值,我们依旧要继续缩减区间,查找是否在左侧还有匹配内容
nums[middle]<target,证明目标值在右边,所以改变left指针,继续在右边查找。
所以根据这个思路,我们稍稍改写经典的二分查找代码,得到代码如下:

        while (left <= right) {int middle = left + ((right - left) / 2);if (nums[middle] >= target) { // 寻找左边界,nums[middle] == target的时候更新rightright = middle - 1;leftBorder = right;} else {left = middle + 1;}}

在原本的二分法代码里,将原本的3种情况改为两种
nums[middle]>=target时,证明左边界一定在middle的左边,这时right=middle-1,然后将right赋值给leftBorder
之所以这样做,是因为当我们找到匹配值的时候,依旧要继续right指针改变,缩减区间,继续探索左边界。
这时候需要将匹配值左边的那个下标保存下来,如果它是左边界,当退出循环时,我们就得到了左边界。
若它不是做边界,例如左边还是target,那么它将继续二分,查找左边界,直到退出循环。

rightBorder:寻找右边界

寻找右边界的方法与寻找左边界的方法类似,代码如下:

        while (left <= right) {int middle = left + ((right - left) / 2);if (nums[middle] > target) {right = middle - 1;} else { // 寻找右边界,nums[middle] == target的时候更新leftleft = middle + 1;rightBorder = left;}}

这样,我们先定义leftBorder和rightBorder,并赋予他们一个初始值,若查找到疑似左/右边界的,他们将被赋值,若未被赋值,则证明情况为target在数组的范围之外:

例如nums = [5,7,7,8,8,10], target = 3
nums[middle]将永远大于target=3
在查找右边界的时候,rightBorder未被赋值,为初始值

例如nums = [5,7,7,8,8,10], target = 12
nums[middle]将永远小于target=12
在查找左边界的时候,leftBorder未被赋值,为初始值
可通过判断是否被赋值来判断是否target超出范围

情况分析及完整代码

综上,我们知道了如何处理变种的二分法。但所有的情况还没有考虑。
我们将leftBorderrightBorder赋予初始值-2
情况一

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

根据前面的代码,我们已经知道leftBorderrightBorder指向的是左/右范围的上一位/下一位
所以在这种情况下,leftBorder=2,rightBorder=5
由于若想查找到目标值,则需要至少一个目标值在数组之中,那么rightBorder减去leftBorder要大于1
因此条件为rightBorder-leftBorder>1

情况三

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

前面已经分析过,leftBorderrightBorder为初始值时候,代表超过范围

情况二

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

这两种情况以外的情况

由此我们的完整代码如下:

class Solution {int[] searchRange(int[] nums, int target) {int leftBorder = getLeftBorder(nums, target);int rightBorder = getRightBorder(nums, target);// 情况一if (leftBorder == -2 || rightBorder == -2) return new int[]{-1, -1};// 情况三if (rightBorder - leftBorder > 1) return new int[]{leftBorder + 1, rightBorder - 1};// 情况二return new int[]{-1, -1};}int getRightBorder(int[] nums, int target) {int left = 0;int right = nums.length - 1;int rightBorder = -2; // 记录一下rightBorder没有被赋值的情况while (left <= right) {int middle = left + ((right - left) / 2);if (nums[middle] > target) {right = middle - 1;} else { // 寻找右边界,nums[middle] == target的时候更新leftleft = middle + 1;rightBorder = left;}}return rightBorder;}int getLeftBorder(int[] nums, int target) {int left = 0;int right = nums.length - 1;int leftBorder = -2; // 记录一下leftBorder没有被赋值的情况while (left <= right) {int middle = left + ((right - left) / 2);if (nums[middle] >= target) { // 寻找左边界,nums[middle] == target的时候更新rightright = middle - 1;leftBorder = right;} else {left = middle + 1;}}return leftBorder;}

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

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

相关文章

【3D Slicer】的小白入门使用指南九

定量医学影像临床研究与实践 任务 定量成像教程 定量成像是从医学影像中提取定量测量的过程。 本教程基于两个定量成像的例子构建: - 形态学:缓慢生长肿瘤中的小体积变化 - 功能:鳞状细胞癌中的代谢活动 第1部分:使用变化跟踪模块测量脑膜瘤的小体积变化第2部分:使用PET标…

为什么要使用Ansible实现Linux管理自动化?

自动化和Linux系统管理 多年来&#xff0c;大多数系统管理和基础架构管理都依赖于通过图形或命令行用户界面执行的手动任务。系统管理员通常使用清单、其他文档或记忆的例程来执行标准任务。 这种方法容易出错。系统管理员很容易跳过某个步骤或在某个步骤上犯错误。验证这些步…

C# 实现对指定句柄的窗口进行键盘输入的实现

在C#中实现对指定句柄的窗口进行键盘操作&#xff0c;可以通过多种方式来实现。以下是一篇详细的指南&#xff0c;介绍如何在C#中实现这一功能。 1. 使用Windows API函数 在C#中&#xff0c;我们可以通过P/Invoke调用Windows API来实现对指定窗口的键盘操作。以下是一些关键的…

JavaWeb--MySQL

1. MySQL概述 首先来了解一下什么是数据库。 数据库&#xff1a;英文为 DataBase&#xff0c;简称DB&#xff0c;它是存储和管理数据的仓库。 像我们日常访问的电商网站京东&#xff0c;企业内部的管理系统OA、ERP、CRM这类的系统&#xff0c;以及大家每天都会刷的头条、抖音…

Qt学习笔记(四)多线程

系列文章目录 Qt开发笔记&#xff08;一&#xff09;Qt的基础知识及环境编译&#xff08;泰山派&#xff09; Qt学习笔记&#xff08;二&#xff09;Qt 信号与槽 Qt学习笔记&#xff08;三&#xff09;网络编程 Qt学习笔记&#xff08;四&#xff09;多线程 文章目录 系列文章…

Elasticsearch 8.16:适用于生产的混合对话搜索和创新的向量数据量化,其性能优于乘积量化 (PQ)

作者&#xff1a;来自 Elastic Ranjana Devaji, Dana Juratoni Elasticsearch 8.16 引入了 BBQ&#xff08;Better Binary Quantization - 更好的二进制量化&#xff09;—— 一种压缩向量化数据的创新方法&#xff0c;其性能优于传统方法&#xff0c;例如乘积量化 (Product Qu…

Flume和kafka的整合

1、Kafka作为Source 【数据进入到kafka中&#xff0c;抽取出来】 在flume的conf文件夹下&#xff0c;有一个flumeconf 文件夹&#xff1a;这个文件夹是自己创建的 创建一个flume脚本文件&#xff1a; kafka-memory-logger.conf Flume 1.9用户手册中文版 — 可能是目前翻译最完…

现代密码学|古典密码学例题讲解|AES数学基础(GF(2^8)有限域上的运算问题)| AES加密算法

文章目录 古典密码凯撒密码和移位变换仿射变换例题多表代换例题 AES数学基础&#xff08;GF&#xff08;2^8&#xff09;有限域上的运算问题&#xff09;多项式表示法 | 加法 | 乘法X乘法模x的四次方1的乘法 AES加密算法初始变换字节代换行移位列混合轮密钥加子密钥&#xff08…

3. Spring Cloud Eureka 服务注册与发现(超详细说明及使用)

3. Spring Cloud Eureka 服务注册与发现(超详细说明及使用) 文章目录 3. Spring Cloud Eureka 服务注册与发现(超详细说明及使用)前言1. Spring Cloud Eureka 的概述1.1 服务治理概述1.2 服务注册与发现 2. 实践&#xff1a;创建单机 Eureka Server 注册中心2.1 需求说明 图解…

视频孪生技术在金融银行网点场景中的应用价值

作为国民经济重要的基础行业&#xff0c;金融行业在高速发展的同时衍生出业务纠纷、安全防范、职能管理等诸多问题&#xff0c;对安全防范和监督管理提出了更高的要求。因此&#xff0c;如何能更好的利用视频监控系统价值&#xff0c;让管理人员更简便的浏览监控视频、更快速的…

武汉EI学术会议一览表

武汉近期将举办BDDM2024大数据会议、ASIM2024智能制造会议、ICSGPS2025电网会议&#xff0c;吸引国内外学者参与&#xff0c;推动科技创新与产业发展&#xff0c;录用论文将提交EI索引。 武汉EI学术会议一览表 1.第二届大数据与数据挖掘国际会议&#xff08;BDDM 2024&#xf…

LeetCode Hot100 15.三数之和

题干&#xff1a; 思路&#xff1a; 首先想到的是哈希表&#xff0c;类似于两数之和的想法&#xff0c;共两层循环&#xff0c;将遍历到的第一个元素和第二个元素存入哈希表中&#xff0c;然后按条件找第三个元素&#xff0c;但是这道题有去重的要求&#xff0c;哈希表实现较为…

html + css 自适应首页布局案例

文章目录 前言一、组成二、代码1. css 样式2. body 内容3.全部整体 三、效果 前言 一个自适应的html布局 一、组成 整体居中&#xff0c;宽度1200px&#xff0c;小屏幕宽度100% 二、代码 1. css 样式 代码如下&#xff08;示例&#xff09;&#xff1a; <style>* {…

使用Axios函数库进行网络请求的使用指南

目录 前言1. 什么是Axios2. Axios的引入方式2.1 通过CDN直接引入2.2 在模块化项目中引入 3. 使用Axios发送请求3.1 GET请求3.2 POST请求 4. Axios请求方式别名5. 使用Axios创建实例5.1 创建Axios实例5.2 使用实例发送请求 6. 使用async/await简化异步请求6.1 获取所有文章数据6…

MySQL —— MySQL索引介绍、索引数据结构、聚集索引和辅助索引、索引覆盖

文章目录 索引概念索引分类索引数据结构种类Innodb 索引数据结构聚集索引和辅助索引&#xff08;非聚集索引&#xff09;聚集索引辅助索引&#xff08;非聚集索引&#xff09; 索引覆盖 索引概念 索引是对数据库表中一列或多列的值进行排序后的一种数据结构。用于帮助 mysql 提…

python实现十进制转换二进制,tkinter界面

目录 需求 效果 代码实现 代码解释 需求 python实现十进制转换二进制 效果 代码实现 import tkinter as tk from tkinter import messageboxdef convert_to_binary():try:# 获取输入框中的十进制数decimal_number int(entry.get())# 转换为二进制binary_number bin(de…

机器学习—正则化和偏差或方差

正则化参数的选择对偏差和方差的影响 用一个四阶多项式&#xff0c;要用正则化拟合这个模型&#xff0c;这里的lambda的值是正则化参数&#xff0c;它控制着你交易的金额&#xff0c;保持参数w与训练数据拟合&#xff0c;从将lambda设置为非常大的值的示例开始&#xff0c;例如…

在Node.js中如何使用TypeScript

第一步&#xff1a;创建一个Node.js项目的package.json文件 npm init -y第二步&#xff1a;添加TypeScript、添加node.d.ts npm install typescript -D npm install types/node -D第三步&#xff1a;初始化一个tsconfig.json文件 npx tsc --init --rootDir src --outDir lib…

零基础Java第十九期:认识String(一)

目录 一、String的重要性 二、String的常用方法 2.1. 字符串构造 2.2. String对象的比较 2.3. 字符串查找 2.4. 转化 2.4. 字符串替换 2.5. 字符串拆分 2.6. 字符串截取 一、String的重要性 在C语言中已经涉及到字符串了&#xff0c;但是在C语言中要表示字符串只能…

面试经典 150 题:20、2、228、122

20. 有效的括号 参考代码 #include <stack>class Solution { public:bool isValid(string s) {if(s.size() < 2){ //特判&#xff1a;空字符串和一个字符的情况return false;}bool flag true;stack<char> st; //栈for(int i0; i<s.size(); i){if(s[i] ( |…