在排序数组中查找元素的第一个和最后一个位置(Java详解)

news2024/11/23 22:14:10

一、题目描述

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

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

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

示例:

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

输出:[3, 4]

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

输出:[-1, -1]

输入:nums = [ ],target = 0

输出:[-1, -1]

二、题解

思路分析:

题目要求我们找到出现target的第一个位置和最后一个位置,首先,我们想到可以通过暴力枚举的方法来解决该问题,即遍历数组,并记录target第一次出现和最后一次出现的位置。然而,题目要求我们实现时间复杂度为O(log n)的算法,且题目中给出的数组为非递减的数组,因此,我们可以考虑使用二分查找的方法来解决该问题

由于题目中数据量较小,使用遍历的方法也可以通过该题

遍历代码:

class Solution {
    public int[] searchRange(int[] nums, int target) {
        int first = -1;
        int last = -1;
        boolean flg = false;//判断是否是第一个位置
        for (int i = 0; i < nums.length; i++) {
            //第一个位置
            if(nums[i] == target && !flg){
                first = i;
                flg = true;
            }
            //最后一个位置
            //注意处理特殊情况,即最后一个元素在最后一个位置时,nums[i+1]越界
           if(nums[i] == target && (i == nums.length - 1 || nums[i+1] != target)){
                last = i;
            }
        }
        int[] ret = {first,last};
        return ret;
    }
}

如何使用二分查找来解决该问题?

题目要求我们找到target在数组中第一次出现和最后一次出现的位置,利用数组非递减的性质

首先我们查找元素的第一个位置:

 我们可通过target将数组分为两部分

其中,左边部分为小于target的元素,右边部分为大于等于target的元素,由于右边区域大于等于target,因此区域最左边的值即为target第一次出现的位置,即右边区域的左端点为所求结果

我们定义left指向0位置,right指向最后一个元素,mid指向中间位置

若mid指向的元素落在右边区间,此时nums[mid]大于等于target,需要更新right的值,由于要找的结果(target第一次出现的位置)在此区间内,即mid所指向的位置可能就是最终结果,因此不能将right更新为mid - 1,而应更新为mid

若mid所指向的元素落在左边区间,此时nums[mid]小于target,需要更新 left 的值,由于要找的结果不在此区间内,因此可将left的结果更新为 mid + 1

 当left和right之间元素为偶数个时,此时中间元素有两个,应该选择哪一个作为中间元素?

由于我们查找的是右边区间内最左边的元素,因此,应该选择左边的元素作为中间元素

若选择右边元素作为中间元素,能够成功查找到结果吗? 

当选择右边元素作为中间元素时,此时会出现死循环的情况,例如: 

 上图中,当选择右边元素作为中间元素时,mid指向的元素落在右边区间,此时将right更新为mid,再求mid,此时mid仍为指向刚才位置,即落在右边区间,此时再次更新right为mid,再次求mid... 从而死循环

循环条件如何设置?

 由于我们将right更新为mid,因此循环的条件应为left < right,若循环条件设置为left <= right,当left = right时,此时找到结果,而结果落在右边区间,此时会更新right的值,而right 更新为mid,即当前位置,从而死循环

分析完以上问题后,我们可以尝试编写查找右边区域最左边元素的代码:

//查找target第一次出现的位置(右边区间的左端点)
int left = 0,right = nums.length - 1,mid = left + (right - left)/2;
//循环条件应设置为left < right 
//不能设置为left <= right,否则会死循环
while(left < right){
    //当mid所指向的元素落在左边区间时,更新left
    if(nums[mid] < target){
        left = mid + 1;
    }else{//当mid所指向的元素落在右边区间时,此时更新right
        //由于右边区间的元素大于等于target,即结果在该区间内,
        // 因此不能将right更新为mid - 1,而应更新为mid
        right = mid;
    }
    //更新mid,当有两个中间元素时,mid应指向其中左边的元素
    mid = left + (right - left) / 2;
}

 此时我们查找target最后一次出现的位置

与查找第一次出现位置的思路相同,我们首先将数组分为两个部分:

其中,左边区间内元素小于等于target,右边区间元素大于target

此时,要找的结果即为左边区间的右端点

 同样的,定义left指向0位置,right指向最后一个元素,mid指向中间位置

若mid所指向的元素落在左边区间,此时需要更新left的值,由于要找的结果落在此区间内,即mid所指向的位置可能就是最终结果,因此不能将left更新为mid + 1,而应更新为mid

若mid所指向的元素落在右边区间,此时需要更新right的值,由于要找的结果不在右边区间,因此可将right的值更新为mid - 1

当left和right之间元素为偶数个时,此时中间元素有两个,应该选择哪一个作为中间元素?

 由于我们查找的是左边区间内最右边的元素,因此,应该选择右边的元素作为中间元素

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

同样的,当选择左边元素作为中间元素时,也会造成死循环

此时left的值一直更新为当前位置,造成死循环

循环条件的设置:

循环条件也同样应该设置为left < right,否则会死循环

此时我们尝试编写查找左边区间右端点代码:

//查找区间右端点
left = mid;
right = nums.length - 1;
mid = left + (right - left + 1)/2;
while(left < right){
    //当mid所指向的值落在右边区域时,更新右端点
    if(nums[mid] > target){
        right = mid - 1;
    }else{//当mid所指向的值落在左边区域时,更新左端点
        //由于左边区间的元素小于等于target,即结果在该区间内,
        //因此不能将left更新为mid + 1,而应更新为mid
        left = mid;
    }
    //更新mid的值,若有两个中间元素时,mid应指向其中右边的元素
    mid = left + (right - left + 1) / 2;
}

完整代码:

class Solution {
    public int[] searchRange(int[] nums, int target) {
        int[] ret = {-1,-1};
         //若数组为空,直接返回ret
        if(nums.length == 0){
            return ret;
        }
        //查找target第一次出现的位置(右边区间的左端点)
        int left = 0,right = nums.length - 1,mid = left + (right - left) / 2;
        //循环条件应设置为left < right
        //不能设置为left <= right,否则会死循环
        while(left < right){
            //当mid所指向的元素落在左边区间时,更新left
            if(nums[mid] < target){
                left = mid + 1;
            }else{//当mid所指向的元素落在右边区间时,此时更新right
                //由于右边区间的元素大于等于target,即结果在该区间内,
                // 因此不能将right更新为mid - 1,而应更新为mid
                right = mid;
            }
            //更新mid,当有两个中间元素时,mid应指向其中左边的元素
            mid = left + (right - left) / 2;
        }
        //此时left = right = mid,使用哪一个变量进行判断和更新都可以
        //若数组中无值为target的元素,直接返回ret
        if(nums[left] == target){
            ret[0] = left;
        }else{
            return ret;
        }

        //查找区间右端点
        left = mid;
        right = nums.length - 1;
        mid = left + (right - left + 1)/2;
        while(left < right) {
            //当mid所指向的值落在右边区域时,更新右端点
            if (nums[mid] > target) {
                right = mid - 1;
            } else {//当mid所指向的值落在左边区域时,更新左端点
                //由于左边区间的元素小于等于target,即结果在该区间内,
                //因此不能将left更新为mid + 1,而应更新为mid
                left = mid;
            }
            //更新mid的值,若有两个中间元素时,mid应指向其中右边的元素
            mid = left + (right - left + 1) / 2;
        }
        ret[1] = left;
        return ret;
    }
}

题目来自: 

34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode)

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

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

相关文章

verilog语法进阶,时钟原语

概述&#xff1a; 内容 1. 时钟缓冲 2. 输入时钟缓冲 3. ODDR2作为输出时钟缓冲 1. 输入时钟缓冲 BUFGP verilog c代码&#xff0c;clk作为触发器的边沿触发&#xff0c;会自动将clk综合成时钟信号。 module primitive1(input clk,input a,output reg y); always (posed…

深度剖析JavaScript中冒泡和捕获机制、事件代理

JS事件传播的两种机制包括冒泡和捕获&#xff0c;下面将具体剖析它们之间本质的区别。 事件冒泡: 先触发子元素的事件&#xff0c;再触发父元素的事件。 创建一个 ul label 和 li label, 分别绑定一个父id 和 子 id, 再通过创建 script&#xff0c;去绑定各自的点击事件。 <…

Threejs利用着色器编写动态飞线特效

一、导语 动态飞线特效是可视化数据地图中常见的需求之一&#xff0c;鼠标点击的区块作为终点&#xff0c;从其他区块飞线至点击区块&#xff0c;附带颜色变换或者结合粒子动画 二、分析 利用创建3点来构成贝塞尔曲线&#xff0c;形成线段利用着色器材质来按照线段以及时间…

Linux---压缩和解压缩命令

1. 压缩格式的介绍 Linux默认支持的压缩格式: .gz.bz2.zip 说明: .gz和.bz2的压缩包需要使用tar命令来压缩和解压缩.zip的压缩包需要使用zip命令来压缩&#xff0c;使用unzip命令来解压缩 压缩目的: 节省磁盘空间 2. tar命令及选项的使用 命令说明tar压缩和解压缩命令 …

SE考研真题总结(三)

继续更新&#xff0c;今天准备连出两期该系列~ SE考研真题总结&#xff08;二&#xff09;https://blog.csdn.net/jsl123x/article/details/134857052?spm1001.2014.3001.5501 目录 一.简答题 二.代码大题 一.简答题 1.工程和科学的区别 科学是关于事物的基本原理和事实的…

代码随想录算法训练营 | day53 动态规划 1143.最长公共子序列,1035.不相交的线,53.最大子序和

刷题 1143.最长公共子序列 题目链接 | 文章讲解 | 视频讲解 题目&#xff1a;给定两个字符串 text1 和 text2&#xff0c;返回这两个字符串的最长公共子序列的长度。 一个字符串的 子序列 是指这样一个新的字符串&#xff1a;它是由原字符串在不改变字符的相对顺序的情况下…

Android动画(四)——属性动画ValueAnimator的妙用

目录 介绍 效果图 代码实现 xml文件 介绍 ValueAnimator是ObjectAnimator的父类&#xff0c;它继承自Animator。ValueAnimaotor同样提供了ofInt、ofFloat、ofObject等静态方法&#xff0c;传入的参数是动画过程的开始值、中间值、结束值来构造动画对象。可以将ValueAnimator看…

C#深拷贝效率对比

对于浅拷贝和深拷贝&#xff0c;前面的文章已经说明了。 C#浅拷贝和深拷贝数据-CSDN博客 本篇说一下&#xff0c;深拷贝的效率问题&#xff0c;效率一直是程序追求的&#xff0c;效率越高肯定越好&#xff0c;有时候功能是实现了&#xff0c;但是运行以及处理数据的效率非常低…

【算法】bfs与dfs算法解决FloodFill(洪流)问题(C++)

文章目录 1. 什么是FloodFill问题2. 用什么方法解决FloodFill问题3. 具体例题773.图像渲染200.岛屿数量695.岛屿的最大面积130.被围绕的区域 1. 什么是FloodFill问题 一般floodfill问题可以描述为&#xff1a;给定一个二维矩阵&#xff0c;其中每个元素代表一个像素点&#xf…

Python-flask 入门代码

python与pycharm安装 过程略&#xff0c;网上很多&#xff0c;记得为pycharm配置默认解释器 虚拟环境 pipenv # 全局安装虚拟环境 # 可加-U参数&#xff0c;明确全局安装&#xff0c;不加好像也可以? pip3 install pipenv #检查安装情况 pipenv --version # ---控制台输出…

跨域的解决方式(java后端)

文章目录 一、跨域介绍1、什么是跨域2、为什么会产生跨域3、禁止跨域的原因 二、简单请求和非简单请求1、简单请求1.1、什么时简单请求1.2、简单请求基础流程 2、非简单请求2.1、预检请求2.2、预检请求的回应2.3、浏览器的正常请求和回应 3、自定义跨域过滤器 三、解决方式1、C…

Java基础语法之抽象类和接口

抽象类 什么是抽象类 并不是所有的类都是用来描述对象的&#xff0c;这样的类就是抽象类 例如&#xff0c;矩形&#xff0c;三角形都是图形&#xff0c;但图形类无法去描述具体图形&#xff0c;所以它的draw方法无法具体实现&#xff0c;这个方法就可以没设计成抽象方法&…

003 Windows用户与组管理

Windows用户管理 一、用户账户 1、什么是用户账户 不同用户身份拥有不同的权限每个用户包含了一个名称和一个密码每个用户账户具有唯一的安全标识符查看系统中的用户 net user 安全标识符&#xff08;SID&#xff09; whoami /user 使用注册表查看 打开注册表命令regedi…

Sentinel使用详解

组件简介 Sentinel是阿里开源的一套用于服务容错的综合性解决方案。它以流量为切入点&#xff0c;从流量控制、熔断降级、系统负载保护等多个维度来保护服务的稳定性。Sentinel承接了阿里巴巴近10年的双十一大促流量的核心场景&#xff0c;例如秒杀、消息削峰填谷、集群流量控…

Java集合-12

Map的接口特点 key重复时会进行替换 package com.edu.map;import java.util.HashMap; import java.util.Map; import java.util.Set;SuppressWarnings({"all"}) public class Demo01 {public static void main(String[] args) {Map map new HashMap<>();map.…

并查集<基于ranks 的优化,基于Path Spliting的优化>

需求分析 假设有n个村庄&#xff0c;有些村庄之间有连接的路&#xff0c;有些村庄之间并没有连接的路 请你设计一个数据结构&#xff0c;能够快速执行2个操作 ◼ 查询2个村庄之间是否有连接的路◼ 连接2个村庄 首先思考在现有的数据结构能否实现上面的功能&#xff0c;数组、…

JVM学习之运行时数据区

运行时数据区 概述 内存 内存是非常重要的系统资源&#xff0c;是硬盘和CPU的中间桥梁&#xff0c;承载着操作系统和应用程序的实时运行。JVM内存布局规定了Java在运行过程中内存申请&#xff0c;分配&#xff0c;管理的策略&#xff0c;保证了JVM高效稳定运行。不同的JVM对于…

HTTP 302错误:临时重定向

在Web开发中&#xff0c;HTTP状态码是用于表示Web服务器响应的各种状态。其中&#xff0c;HTTP 302错误表示临时重定向&#xff0c;这意味着请求的资源已被临时移动到其他位置&#xff0c;并且服务器已经提供了新的URL&#xff0c;以便客户端可以重新发送请求。 了解HTTP 302错…

[计网01] 物理层 详细解析笔记,特性

计算机网络的物理层是网络协议栈中的第一层&#xff0c;负责传输原始的比特流&#xff08;bitstream&#xff09;通过物理媒介进行通信。物理层主要关注传输介质、信号的编码和调制、数据传输速率以及数据传输的物理连接等方面。 相关特性 机械特性&#xff08;Mechanical Ch…

网络安全—学习溯源和日志分析

日志分析的步骤&#xff1a; 判断是否为攻击行为 不是&#xff1a;不用处理 是&#xff1a;判断攻击是否成功或者失败 攻击失败&#xff1a;判断IP地址是否为恶意地址&#xff0c;可以让防火墙过滤IP地址 攻击成功&#xff1a;做应急处置和溯源分析 应急处置&#xff1a;网络下…