在排序数组中查找元素的第一个和最后一个位置[中等]

优质博文IT-BLOG-CN

一、题目

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

二、代码

方案一:二分查找

直观的思路肯定是从前往后遍历一遍。用两个变量记录第一次和最后一次遇见target的下标,但这个方法的时间复杂度为O(n),没有利用到数组升序排列的条件。

由于数组已经排序,因此整个数组是单调递增的,我们可以利用二分法来加速查找的过程。

考虑target开始和结束位置,其实我们要找的就是数组中「第一个等于target的位置」(记为leftIdx)和「第一个大于target的位置减一」(记为rightIdx)。

二分查找中,寻找leftIdx即为在数组中寻找第一个大于等于target的下标,寻找rightIdx即为在数组中寻找第一个大于target的下标,然后将下标减一。两者的判断条件不同,为了代码的复用,我们定义binarySearch(nums, target, lower)表示在nums数组中二分查找target的位置,如果lowertrue,则查找第一个大于等于target的下标,否则查找第一个大于target的下标。

最后,因为target可能不存在数组中,因此我们需要重新校验我们得到的两个下标leftIdxrightIdx,看是否符合条件,如果符合条件就返回[leftIdx,rightIdx],不符合就返回[−1,−1]

class Solution {
    public int[] searchRange(int[] nums, int target) {
        int leftIdx = binarySearch(nums, target, true);
        int rightIdx = binarySearch(nums, target, false) - 1;
        if (leftIdx <= rightIdx && rightIdx < nums.length && nums[leftIdx] == target && nums[rightIdx] == target) {
            return new int[]{leftIdx, rightIdx};
        } 
        return new int[]{-1, -1};
    }

    public int binarySearch(int[] nums, int target, boolean lower) {
        int left = 0, right = nums.length - 1, ans = nums.length;
        while (left <= right) {
            int mid = (left + right) / 2;
            if (nums[mid] > target || (lower && nums[mid] >= target)) {
                right = mid - 1;
                ans = mid;
            } else {
                left = mid + 1;
            }
        }
        return ans;
    }
}

在这里插入图片描述

方案二:小于等于目标值的搜索策略

在这里插入图片描述

class Solution {
    public int[] searchRange(int[] nums, int target) {
        // 首个target如果存在,一定是最后小于等于target-1的元素的后一位
        int start = search(nums, target - 1) + 1;
        if(start == nums.length || nums[start] != target){
            return new int[]{-1, -1};    // 首个target不存在,即数组中不包含target
        }
        // 找到最后小于target的元素,即为最后一个target
        int end = search(nums, target);
        return new int[]{start, end};
    }

    /**
     * 返回最后小于等于target的元素索引,如果不存在,返回-1
     * @param nums: 输入数组
     * @param target: 目标值
     * @return: 目标值索引
    */
    private int search(int[] nums, int target){
        // 二分查找区间[left, right),初始为整个区间
        int left = 0;   
        int right = nums.length;
        // 找到最后小于等于target的值
        while(left < right){
            int mid = left + ((right - left) >> 1);
            if(nums[mid] <= target){
                left = mid + 1;  // 找到一个小于等于target的值,暂存并在右半区间继续查找更大的小于target的值
            }else{
                right = mid;    // 没有找到小于等于target的值,则在左半区间去寻找更小的数
            }
        }
        return left - 1;    // left始终为暂存结果的后一位
    }
}

方案三:两次二分法查找左右边界

这道题目可以使用两次二分查找来分别确定左右边界。这里以左边界为例,我们判断一个位置是左边界需要满足以下两个条件:
【1】该位置值为target
【2】该位置左边的元素小于他,或者该位置的下标为0

所以,根据这个思路,只需要简单的修改一下二分查找的逻辑就可以找到左边界,代码如下:

def searchLeft(nums, target):
            # 左边界需要满足两个条件:
            # 1. 他的值为target
            # 2. 他的左边元素小于他,或者他的下标为0
            left, right = 0, len(nums)-1
            while left <= right:
                mid = (right-left)//2 + left
                if nums[mid] == target:
                    if mid == 0 or nums[mid-1] < target:
                        return mid
                    else:
                        right = mid - 1
                elif nums[mid] > target:
                    right = mid - 1
                else:
                    left = mid + 1
            return -1

整体思路只是在if nums[mid] == target时多加了一步判断,如果不是起始位置,那么起始位置一定在mid左侧,所以需要同时修改right = mid - 1

同样的,右边界的位置需要满足:
【1】该位置值为target
【2】该位置右边的元素小于他,或者该位置的下标为len(nums)-1

也只需要稍微修改一下二分查找的代码就可以,最后完整代码如下:

class Solution:
    def searchRange(self, nums: List[int], target: int) -> List[int]:
        def searchLeft(nums, target):
            # 左边界需要满足两个条件:
            # 1. 他的值为target
            # 2. 他的左边元素小于他,或者他的下标为0
            left, right = 0, len(nums)-1
            while left <= right:
                mid = (right-left)//2 + left
                if nums[mid] == target:
                    if mid == 0 or nums[mid-1] < target:
                        return mid
                    else:
                        right = mid - 1
                elif nums[mid] > target:
                    right = mid - 1
                else:
                    left = mid + 1
            return -1
        
        def searchRight(nums, target):
            # 右边界需要满足两个条件:
            # 1. 他的值为target
            # 2. 他的右边元素大于他,或者他的下标为len(nums)-1
            left, right = 0, len(nums)-1
            while left <= right:
                mid = (right-left)//2 + left
                if nums[mid] == target:
                    if mid == len(nums)-1 or nums[mid+1] > target:
                        return mid
                    else:
                        left = mid + 1
                elif nums[mid] > target:
                    right = mid - 1
                else:
                    left = mid + 1
            return -1

        return [searchLeft(nums, target), searchRight(nums, target)]

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Cookie 探秘:了解 Web 浏览器中的小甜饼

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

华为智慧教室3.0的晨光,点亮教育智能化变革

“教室外有更大的世界&#xff0c;但世界上没有比教室更伟大的地方。” 我们在求学阶段&#xff0c;都听说过这句话&#xff0c;但往往是在走出校园之后&#xff0c;才真正理解了这句话。为了让走出校园的孩子能够有能力&#xff0c;有勇气探索广阔的世界。我们应该准备最好的教…

【Leetcode】1588.所有奇数长度子数组的和

题目描述 思路 题目要求我们求解所有奇数长度数组的和。若暴力循环求解&#xff0c;时间复杂度过高。所以&#xff0c;我们可以采用前缀和优化。 如上图输入arr数组&#xff0c;sum[i]用于计算arr数组中前i个数的和。(在程序中&#xff0c;先给sum[0]赋值&#xff0c;等于arr[0…

平台总线式驱动开发

一、总线、设备、驱动 硬编码式的驱动开发带来的问题&#xff1a; 垃圾代码太多 结构不清晰 一些统一设备功能难以支持 开发效率低下 1.1 初期解决思路&#xff1a;设备和驱动分离 struct device来表示一个具体设备&#xff0c;主要提供具体设备相关的资源&#xff08;如…

Java项目:37 springboot003图书个性化推荐系统的设计与实现

作者主页&#xff1a;源码空间codegym 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文中获取源码 项目介绍 springboot003图书个性化推荐系统的设计与实现 管理员&#xff1a;首页、个人中心、学生管理、图书分类管理、图书信息管理、图书预约管理、退…

阿里二面,redis宕机了,如何快速恢复数据

背景 有个同学阿里二面&#xff0c;面试官问&#xff1a;redis宕机了&#xff0c;如何恢复数据&#xff1f; 这位同学当时一脸懵&#xff0c;不知道如何回答。 分析分析这个问题&#xff0c;redis宕机&#xff0c;要想恢复数据&#xff0c;首先redis的数据有没有做持久化&…

基于Java springboot+VUE+redis实现的前后端分类版网上商城项目

基于Java springbootVUEredis实现的前后端分类版网上商城项目 博主介绍&#xff1a;多年java开发经验&#xff0c;专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 央顺技术团队 Java毕设项目精品实战案例《1000套》 欢迎点赞 收藏 ⭐留言…

《操作系统真相还原》读书笔记五:mbr初体验

1.编写mbr汇编程序 SECTION MBR vstart0x7c00mov ax,csmov ds,axmov es,axmov ss,axmov fs,axmov sp,0x7c00; 清屏mov ax,0x600mov bx,0x700mov cx,0mov dx, 0x184fint 0x10; 设置光标结束mov ah,3mov bh,0int 0x10mov ax,messagemov bp,axmov cx,5mov ax,0x1301mov bx,0x2 ;…

2.14ALU,存储系统

IR存放当下欲执行的指令&#xff1b;PC存放下一条指令的地址&#xff1b; MAR存放欲访问的存储单元地址&#xff1b;MDR存放从存储单元取来的数据&#xff01; 地址译码器是主存的构成部分&#xff0c;不属于CPU&#xff1b;地址寄存器虽然一般属于主存&#xff0c;但是现代计…

联通移动电信卡推广分销开源版

联通移动电信卡推广分销开源版 一套完善&#xff0c;多功能&#xff0c;的号卡分销系统&#xff0c;多接口&#xff0c;包括运营商接口&#xff0c;无限三级代理&#xff0c; 目前市面上最优雅的号卡系统 自动安装向导 易于安装使用部署 多个第三方接口资源汇聚 &#xff0c;全…

华为昇腾系列——入门学习

概述 昇腾&#xff08;Ascend&#xff09;是华为推出的人工智能处理器品牌&#xff0c;其系列产品包括昇腾910和昇腾310芯片等。 生态情况 众所周知&#xff0c;华为昇腾存在的意义就是替代英伟达的GPU。从事AI开发的小伙伴&#xff0c;应该明白这个替代&#xff0c;不仅仅是…

【Tauri】(4):使用Tauri1.5版本+candle框架运行大模型,前后的搭建运行成功,整合前端项目

1&#xff0c;视频地址 关于tauri 框架 2&#xff0c;搭建rust 环境 # 设置服务器国内代理&#xff1a; export RUSTUP_DIST_SERVER"https://rsproxy.cn" export RUSTUP_UPDATE_ROOT"https://rsproxy.cn/rustup"# 设置环境变量 export RUSTUP_HOME/data/…

Hadoop 3.1.1 分布式搭建过程

准备工作 通过克隆获得三台虚拟机 准备工作&#xff1a;时间同步、时区调整、JDK1.8环境、配置主机名、关闭防火墙、配置静态IP 无特别说明&#xff0c;三台虚拟机都要完成准备工作 1、时间同步 ntpdate ntp.aliyun.com2、调整时区 timedatectl set-timezone Asia/Shanghai3、…

Vue2高级篇

Vue高级 Vue生命周期 生命周期又称为生命周期回调函数、生命周期函数、生命周期钩子, 是Vue在运行过程中的关键时刻帮我们调用的一些指函数, 生命周期函数名字不可修改, 其中的this指向的是vm或组件实例对象. 常用的生命周期钩子: mounted: 发送ajax请求、启动定时器、绑定…

【Vue】VueX仓库

&#x1f4dd;个人主页&#xff1a;五敷有你 &#x1f525;系列专栏&#xff1a;Vue ⛺️稳中求进&#xff0c;晒太阳 目录 Vue概述 是什么 vuex是一个vue的状态管理工具&#xff0c;状态就是数据 大白话&#xff1a;vuex是一个插件&#xff0c;可以帮我们管理vue通用…

BUUCTF---[极客大挑战 2019]BuyFlag1

1.题目描述 2.来到题目链接&#xff0c;先ctrlu打开源码查看&#xff0c;发现有两个可疑的文件 3.打开bay.php发现页面有两个提示 4.第一个提示告诉我们&#xff0c;如果想要买flag的话我们必须是来自cuit的学生并且要输入正确的密码 第二个提示告诉我们要用post方式传passwor…

Jenkins如何做到parameter页面里2个参数的联动

在Jenkins中&#xff0c;参数化构建是一种非常有用的功能&#xff0c;它可以让用户在构建过程中输入参数&#xff0c;从而实现更灵活的构建流程。有时候&#xff0c;我们希望两个参数之间能够实现联动&#xff0c;即一个参数的取值会影响另一个参数的取值。要实现这样的功能&am…

AI大模型:创新前沿的探索之路

AI大模型一直被视为人工智能领域的创新前沿&#xff0c;它们拥有强大的计算能力和学习能力&#xff0c;能够在各种复杂的任务中表现出色。随着技术的不断进步&#xff0c;越来越多的研究者和企业开始投入到AI大模型的研发和应用中&#xff0c;希望能够探索出更多的可能性。 在…

Linux——网络基础

计算机网络背景 网络发展 独立模式: 计算机之间相互独立 在早期的时候&#xff0c;计算机之间是相互独立的&#xff0c;此时如果多个计算机要协同完成某种业务&#xff0c;那么就只能等一台计算机处理完后再将数据传递给下一台计算机&#xff0c;然后下一台计算机再进行相应…

opencv官网 Blob检测

参考&#xff1a;Blob Detection Using OpenCV ( Python, C ) Bolob检测 Blob 是图像中一组连接的像素&#xff0c;它们共享一些共同属性&#xff08;例如&#xff0c;灰度值&#xff09;。在上图中&#xff0c;深色连接区域是 Blob&#xff0c;Blob 检测旨在识别和标记这些区…