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

简介: 在排序数组中查找元素的第一个和最后一个位置


题目

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

解法一

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        int size = nums.size();
        
        //先考虑边界条件
        if(size == 0) return {-1, -1};
        if(nums[0] > target || nums[size-1] < target) return {-1, -1};
        int left = -1, right = size;
         //直接从左端开始遍历,找到第一个和target相等的就是开始位置
        do
            left++;
        while(nums[left] != target && left < size-1); // left < size-1 可以防止越界
        //直接从右端开始遍历,找到第一个和target相等的就是结束位置
        do  
            right--;
        while(nums[right] != target && right >= 1);   
        
            
        // 可能出现left和right遍历一遍都没有找到目标位置的情况,所以需要判断,此时用nums[left]还是nums[right]都可以
        if(nums[left] == target)
            return {left, right};
        else
            return {-1, -1};
    }
};

解法二(二分查找)

代码展示

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        int size = nums.size();
        //处理边界问题
        if(size == 0) return {-1, -1};
        int begin=-1, end=-1;
        // 找二分左端点
        int left = 0, right = size-1;
        while(left < right)
        {
            int mid = left+(right-left)/2;
            if(nums[mid] < target) left = mid+1;
            else right = mid; 
        }
        if(nums[left] == target) begin = left;
        else return {-1, -1};
        //找二分右端点
        left = 0, right = size-1;
        while(left < right)
        {
            int mid = left+(right-left+1)/2;
            if(nums[mid] <= target) left = mid;
            else right = mid-1; 
        }
        if(nums[right] == target) end = right;
        else return {-1, -1};
        return {begin, end};
    }
};

原理剖析

   二分查找需要通过得出这道题目所包含的二段性,来一步一步缩小范围,找到目标值,这道题一个关键的条件就是数组是非递减的,也就意味着数组是递增或者是相等的。二段性怎么找到呢?

   若要找到target,取中心位置为mid,那么就会立刻排出掉一半无关的值,然后再令left=mid,即可缩小排查范围。这样就可以发现这道题使用二分查找来解题的二段性。


首先确定循环结束条件

有两种选择:

  1. while(left < right)
  2. while(left <= right)

区别就在于是否需要当left == right时再进行判断。

  1. 当left == right时, 已经找到了左端点或是右端点,此时不需要冗余的在进行。
  2. 第二点原因之后解释。
    所以选择第一点更为合理。

利用二分找到左端点

  1. nums[mid] < target时, 左端点在右半部分,所以left = mid+1;
  2. nums[mid] > target时, 左端点在左半部分,所以right = mid-1
  3. nums[mid] == target时,此时左端点就在left和right之间,此时不能移动left, 因为也许左端点就在mid的左边,也不能将right移动到mid的左边,因为这两种操作都会使得左端点在二分缩小里面被错过。所以可以移动right, 此时right = mid;
  4. 可以将2,3合并,得到若nums[mid] >= targetright = mid;

利用二分找到右端点

  1. nums[mid] < target时, 右端点在右半部分,所以left = mid+1;
  2. nums[mid] > target时, 右端点在左半部分,所以right = mid-1
  3. nums[mid] == target时,此时右端点就在left和right之间,此时不能移动right, 因为也许右端点就在mid的左边,也不能将right移动到mid的左边,因为这两种操作都会使得左端点在二分缩小里面被错过。所以可以移动right, 此时left = mid;
  4. 可以将1,3合并,得到若nums[mid] <= targetleft = mid;

while(left <= right)导致的死循环问题

   因为当left==right时, 无论是找左端点还是右端点,left 和 right依旧指向mid,永远不会改变,因此如果while(left <= right)的话,就会导致死循环。

mid的取值问题

   通过代码可以看到,mid的取值有两种,分别是:

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

   由于是二分,都是除二,所以上面1代表的意思就是二分后向下取整,2代表的意思就是向上取整。

   下面来看找二分右端点的特殊情况;

   若现在left和right的指向如上图所示,若是mid取值:mid = left+(right-left)/2;,则mid位于1处, 若target就是1, 此时执行的是if(nums[mid] <= target) left = mid;,就会导致left一直指向1,right一直指向不变,没有left<right,while循环就不会结束,就会造成死循环。所以得向上取整,此时mid位于2处, 就不会触发死循环。找左端点使用mid = left+(right-left)/2的原因也是如此。


    😄 创作不易,你的点赞和关注都是对我莫大的鼓励,再次感谢您的观看😄

相关文章
|
10月前
|
机器学习/深度学习 自然语言处理 物联网
FlowMo: 模式搜索+扩散模型提升图像Token化性能
FlowMo是一种基于Transformer的扩散自编码器,无需卷积网络或对抗性损失,在图像Token化领域实现技术突破。它通过两阶段训练(模式匹配预训练与模式搜索后训练)和一维潜在表征,达到低高比特率下的领先性能。FlowMo摒弃传统方法限制,展现卓越重建质量,但推理计算开销较大。其创新为视觉生成系统提供了新方向。
247 4
FlowMo: 模式搜索+扩散模型提升图像Token化性能
|
10月前
|
缓存 JavaScript 前端开发
Dockerfile全面指南:从基础到进阶,掌握容器化构建的核心工具
Dockerfile 是容器化开发中的关键工具。理解并掌握其使用方式,不仅能提高开发效率,还能让你的应用具备更强的可移植性和灵活性。通过优化配置和合理安排构建步骤,可以打造更轻量、更高效的容器镜像。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
消息中间件 Ubuntu Java
Kafka安装部署
Kafka安装部署
|
Linux 应用服务中间件 nginx
21. 【Linux教程】Linux 查看进程
21. 【Linux教程】Linux 查看进程
234 0
|
测试技术 数据库 C++
Qt C++拖放事件探索之旅:多方法深入解析
Qt C++拖放事件探索之旅:多方法深入解析
1438 1
|
消息中间件 存储 Cloud Native
深度剖析 RocketMQ 5.0,架构解析:云原生架构如何支撑多元化场景?
了解 RocketMQ 5.0 的核心概念和架构概览;然后我们会从集群角度出发,从宏观视角学习 RocketMQ 的管控链路、数据链路、客户端和服务端如何交互;学习 RocketMQ 如何实现数据的存储,数据的高可用,如何利用云原生存储进一步提升竞争力。
143317 3
|
C++ Java Go
Java每日一练(20230425) 乘积最大子数组、插入区间、删除有序数组中的重复项II
Java每日一练(20230425) 乘积最大子数组、插入区间、删除有序数组中的重复项II
190 0
Java每日一练(20230425) 乘积最大子数组、插入区间、删除有序数组中的重复项II
|
Prometheus Cloud Native Linux
Prometheus(二)之Node Exporter采集Linux主机数据
Prometheus(二)之Node Exporter采集Linux主机数据
688 0
|
Kubernetes 监控 Cloud Native
fluentd收集kubernetes 集群日志分析
EFK (Elasticsearch + Fluentd + Kibana) 是kubernetes官方推荐的日志收集方案,我们一起了解一下fluentd是如何收集kubernetes集群日志的,庆祝一下fluentd从 CNCF 毕业。开始之前,希望你已经读过Docker 容器日志分析, 本文是其延生的第二篇。
1687 0
fluentd收集kubernetes 集群日志分析
break 语句
break 语句
269 0
break 语句