【小白爬Leetcode34】6.1 在排序数组中查找元素的第一个和最后一个位置 Find First and Last Position of Element in Sorted Array

【小白爬Leetcode34】6.1 在排序数组中查找元素的第一个和最后一个位置 Find First and Last Position of Element in Sorted Array

Leetcode34 m e d i u m \color{#FF4500}{medium} medium
点击进入原题链接:Leetcode34 在排序数组中查找元素的第一个和最后一个位置 Find First and Last Position of Element in Sorted Array

题目

Discription

Given an array of integers numssorted in ascending order, find the starting and ending position of a given targetvalue.

Your algorithm’s runtime complexity must be in the order of O(log n).

If the target is not found in the array, return [-1, -1].
在这里插入图片描述

中文描述

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

你的算法时间复杂度必须是 O(log n) 级别。

如果数组中不存在目标值,返回 [-1, -1]。
在这里插入图片描述

思路 两次二分查找:

由于使用常数次二分查找,因此时间复杂度为O(logn),没有用到额外容器,空间复杂度为O(1)。

实现一:

一次二分查找找左端点,一次二分查找找右端点。

查找左端点的时候,和一般二分查找有区别的地方在于:

  • 当遇到target == nums[mid]的时候,需要判断它是否是左端点,如果mid==0 || nums[mid-1]<target,意味着当前mid到达了数组的边界或者mid的前一个元素的值已经不再是target了,则说明左端点找到了。
  • 如果当前的target == nums[mid]不是左端点,也就是左边还有值等于target的元素,那么此时应该缩小搜索空间,令end = mid-1;
  • 如果target != nums[mid],则和一般的二分查找一样,缩小搜索范围直到找到target != nums[mid]或者begin>end,如果是后者,说明该数组里没有值为target的元素,那么按照返回-1用来构成最终的答案。

同理,在查找右端点的时候,和一般二分查找有区别的地方在于:

  • 当遇到target == nums[mid]的时候,需要判断它是否是右端点,如果mid==nums.size()-1 || nums[mid+1]>target,意味着当前mid到达了数组的边界或者mid的后一个元素的值已经不再是target了,则说明右端点找到了。
  • 如果当前的target == nums[mid]不是右端点,也就是右边还有值等于target的元素,那么此时应该缩小搜索空间,令begin= mid+1;
  • 如果target != nums[mid],则和一般的二分查找一样,缩小搜索范围直到找到target != nums[mid]或者begin>end,如果是后者,说明该数组里没有值为target的元素,那么按照返回-1用来构成最终的答案。

完整代码如下:

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        int left_range = searchLeft(nums,target);
        if(left_range==-1) return *(new vector<int>(2,-1));
        int right_range = searchRight(nums,target); //如果查找左边界没找到target,那么右查找肯定也找不到,因为左查找和右查找只有在找到了target的情况下才有不同的处理,找到target之前二者是一样的,所以此时直接返回[-1,-1]即可。
        return *(new vector<int>{left_range,right_range});
    }
private:
    int searchLeft(vector<int>& nums, int target){
        int begin = 0;
        int end = nums.size()-1;
        while(begin<=end){
            int mid = (begin+end)/2;
            if(target == nums[mid]){
                if(mid==0 || nums[mid-1]<target){//如果是边界了
                    return mid;
                }
                end = mid-1; //如果不是边界,把右边界缩小到mid-1
            }
            //下面和普通的二分查找一样了
            else if(target<nums[mid]){
                end = mid-1;
            }
            else begin = mid+1;
        }
        //如果没找到,说明该元素不存在,直接返回-1
        return -1;
    }
    int searchRight(vector<int>& nums, int target){
        int begin = 0;
        int end = nums.size()-1;
        while(begin<=end){
            int mid = (begin+end)/2;
            if(target == nums[mid]){
                if(mid==nums.size()-1 || nums[mid+1]>target){//如果是边界了
                    return mid;
                }
                begin = mid+1; //如果不是边界,把右边界缩小到mid-1
            }
            //下面和普通的二分查找一样了
            else if(target<nums[mid]){
                end = mid-1;
            }
            else begin = mid+1;
        }
        //如果没找到,说明该元素不存在,直接返回-1
        return -1;
    }
};

在这里插入图片描述

实现二

实现一有一个小问题,第一次找到值为target的元素的这个过程重复了一次,实现二针对这个问题进行了一个改良,首先先找到值为target的那个元素,再以这个点为边界向左和向右去查找边界。

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        int begin = 0;
        int end = nums.size()-1;
        int first_target_idx = -1; //如果找到了target那么就是target的索引,没找到就是-1
        //先找到target再说
        while(begin<=end){
            int mid = (begin+end)/2 ;
            if(target == nums[mid]){
                first_target_idx = mid;
                break;
            }
            else if(target<nums[mid]) end = mid-1;
            else begin = mid+1;
        }
        //如果没找到target可以直接返回[-1,-1]了
        if(first_target_idx==-1) return vector<int>(2,-1);
        //初始化左右边界为第一个找到的target点
        int left_range = first_target_idx;
        int right_range = first_target_idx;
        //记录下此时begin和end的位置备用
        int pre_begin = begin;
        int pre_end = end;
    
        while(left_range-1>=0 && nums[left_range-1]==target){ //左查找
            end = left_range-1;
            while(begin<=end){
                int mid = (begin+end)/2 ;
                if(target == nums[mid]){
                    left_range = mid;
                    break;
                }
                else if(target<nums[mid]) end = mid-1;
                else begin = mid+1;
            }
        }

        end = pre_end;
        while(right_range+1<nums.size() && nums[right_range+1]==target){ //右查找
            begin = right_range+1;
            while(begin<=end){
                int mid = (begin+end)/2 ;
                if(target == nums[mid]){
                    right_range = mid;
                    break;
                }
                else if(target<nums[mid]) end = mid-1;
                else begin = mid+1;
            }
        }
        //返回左查找和右查找的结果
        return vector<int>{left_range,right_range};
    }
};

在这里插入图片描述
不知道哪些很快的解答是怎么来的…

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值