刷题之Leetcode34题(超级详细)

简介: 刷题之Leetcode34题(超级详细)

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]

思路

这道题目如果基础不是很好,不建议大家看简短的代码,简短的代码隐藏了太多逻辑,结果就是稀里糊涂把题AC了,但是没有想清楚具体细节!

对二分还不了解的兄弟先做这两题:

下面我来把所有情况都讨论一下。

寻找target在数组里的左右边界,有如下三种情况:

  • 情况一:target 在数组范围的右边或者左边,例如数组{3, 4, 5},target为2或者数组{3, 4, 5},target为6,此时应该返回{-1, -1}
  • 情况二:target 在数组范围中,且数组中不存在target,例如数组{3,6,7},target为5,此时应该返回{-1, -1}
  • 情况三:target 在数组范围中,且数组中存在target,例如数组{3,6,7},target为6,此时应该返回{1, 1}

这三种情况都考虑到,说明就想的很清楚了。

接下来,在去寻找左边界,和右边界了。

采用二分法来去寻找左右边界,为了让代码清晰,我分别写两个二分来寻找左边界和右边界。

刚刚接触二分搜索的兄弟不建议上来就想用一个二分来查找左右边界,很容易把自己绕进去,建议扎扎实实的写两个二分分别找左边界和右边界

寻找右边界

先来寻找右边界,至于二分查找,如果看过刷题之Leetcode704题(超级详细)-CSDN博客就会知道,二分查找中什么时候用while (left <= right),有什么时候用while (left < right),其实只要清楚循环不变量,很容易区分两种写法。

那么这里我采用while (left <= right)的写法,区间定义为[left, right],即左闭右闭的区间(如果这里有点看不懂了,强烈建议把刷题之Leetcode704题(超级详细)-CSDN博客这篇文章先看了,704题目做了之后再做这道题目就好很多了)

确定好:计算出来的右边界是不包含target的右边界,左边界同理。

可以写出如下代码

 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) {// 寻找右边界,nums[middle] == target的时候更新left
                left = middle + 1;
                rightBorder = left;
                
            } else if(nums[middle]>target){ 
                right = middle - 1;
            }else{
                left=middle-1;
}
        }
        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的时候更新right
                right = middle - 1;
                leftBorder = right;
            } else if(nums[middle]>target){
                right=middle-1;
            }else{ 
                left=middle+1;
}
        }
        return leftBorder;
    }

处理三种情况

左右边界计算完之后,看一下主体代码,这里把上面讨论的三种情况,都覆盖了

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) {// 寻找右边界,nums[middle] == target的时候更新left
                left = middle + 1;
                rightBorder = left;
                
            } else if(nums[middle]>target){ 
                right = middle - 1;
            }else{
                 left=middle+1;
}
        }
        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的时候更新right
                right = middle - 1;
                leftBorder = right;
            } else if(nums[middle]>target){
                right=middle-1;
            }else{ 
                left=middle+1;
}
        }
        return leftBorder;
    }

这份代码在简洁性很有大的优化空间,例如把寻找左右区间函数合并一起。

但拆开更清晰一些,而且把三种情况以及对应的处理逻辑完整的展现出来了。

总结

初学者建议大家一块一块的去分拆这道题目,正如本题解描述,想清楚三种情况之后,先专注于寻找右区间,然后专注于寻找左区间,左右根据左右区间做最后判断。

不要上来就想如果一起寻找左右区间,搞着搞着就会顾此失彼,绕进去拔不出来了。


相关文章
|
11月前
|
Python
Matplotlib 教程 之 Matplotlib 散点图 6
使用 Matplotlib 库中的 `scatter()` 方法绘制散点图,并详细解释了该方法的参数,包括点的大小(`s`)、颜色(`c`)、样式(`marker`)等。此外,还展示了如何使用 `cmap` 参数设置颜色条,以及提供了一个具体的实例代码,演示了如何利用这些参数创建带有颜色渐变的散点图。
106 0
|
机器学习/深度学习 并行计算 PyTorch
PyTorch与DistributedDataParallel:分布式训练入门指南
【8月更文第27天】随着深度学习模型变得越来越复杂,单一GPU已经无法满足训练大规模模型的需求。分布式训练成为了加速模型训练的关键技术之一。PyTorch 提供了多种工具来支持分布式训练,其中 DistributedDataParallel (DDP) 是一个非常受欢迎且易用的选择。本文将详细介绍如何使用 PyTorch 的 DDP 模块来进行分布式训练,并通过一个简单的示例来演示其使用方法。
1884 2
|
机器学习/深度学习 存储 前端开发
大模型应用框架-LangChain(二)
LangChain由 Harrison Chase 创建于2022年10月,它是围绕LLMs(大语言模型)建立的一个框架,LLMs使用机器学习算法和海量数据来分析和理解自然语言,GPT3.5、GPT4是LLMs最先进的代表,国内百度的文心一言、阿里的通义千问也属于LLMs。LangChain自身并不开发LLMs,它的核心理念是为各种LLMs实现通用的接口,把LLMs相关的组件“链接”在一起,简化LLMs应用的开发难度,方便开发者快速地开发复杂的LLMs应用。 LangChain目前有两个语言的实现:python、nodejs。
vue2 自定义 v-model (model选项的使用)
vue2 自定义 v-model (model选项的使用)
1624 1
|
JavaScript 前端开发
第八篇-Javascript 事件
第八篇-Javascript 事件
110 1
|
存储 机器学习/深度学习 编解码
fast.ai 深度学习笔记(七)(2)
fast.ai 深度学习笔记(七)
165 3
fast.ai 深度学习笔记(七)(2)
|
存储 安全 关系型数据库
阿里云容器镜像服务ACR概述(一)
阿里云容器镜像服务ACR概述(一)
|
存储 运维 负载均衡
网络运维主要做什么,日常工作有哪些?
网络运维主要做什么,日常工作有哪些?
1946 0
|
自然语言处理 JavaScript 前端开发
带你读《现代Javascript高级教程》一、作用域和作用域链(1)
带你读《现代Javascript高级教程》一、作用域和作用域链(1)
225 0
|
网络协议
LabVIEW实现Modbus-TCP通信
Modbus协议是一种已广泛应用于当今工业控制领域的通用通讯协议,按其格式可分为Modbus-RTU、Modbus-ASCII和Modbus-TCP。其中,前两者适用于串行通信控制网络中,例如RS485、RS232等,而Modbus-TCP主要应用于基于以太网TCP/IP通信的控制网络中。通过此协议,控制器相互之间、或控制器经由网络(如以太网)可以和其它设备之间进行通信。
LabVIEW实现Modbus-TCP通信