代码随想录-03-二分查找-34.在排序数组中查找元素的第一个和最后一个位置

前言

在本科毕设结束后,我开始刷卡哥的“代码随想录”,每天一节。自己的总结笔记均会放在“算法刷题-代码随想录”该专栏下,以下为“代码随想录”此题原文的链接。
34. 在排序数组中查找元素的第一个和最后一个位置

题目

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

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

进阶:你可以设计并实现时间复杂度为 O ( log ⁡ n ) O(\log n) O(logn) 的算法解决此问题吗?

示例 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]

1. 判断是否使用二分查找的3个前提条件

当题目中包含数据结构为“数组”(比如链表就不可以使用二分查找,因为二分查找只能在内存地址连续的数据结构中进行),并且该“数组为有序数组”以及**“数组中无重复元素”**这三个前提条件,很有可能可以使用“二分查找”。
并且题目还暗示 O ( log ⁡ n ) O(\log n) O(logn)的查找方式,更说明使用二分法进行查找。

2. 本题思路分析:

思路
承接上一题35. 搜索插入位置
这道题也分三种情况。
情况一,数组没有元素,长度为0,则肯定返回[-1,-1]。
情况二包含三种小分类,第一种小分类,target大于所有元素,如数组为{1,2,4},target=0;第二种小分类target小于所有元素,数组中不存在target,如数组为{1,2,4},target=5;
情况二的第三种分类是数组中不存在target,但若按升序插入target将查在数组的两元素之间,如数组为{1,2,4},target=3
情况三有两种小分类,第一个小分类是数组中含有多个target元素,如{1,2,2,4},target=2 ;第二个小分类是只含有一个target元素,如{1,2,2,4},target=4。
各个情况都举一个例子,图例如下。
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述

3. 算法实现

  • 定义target在数组中的范围区间:通过两个int类型,left、right变量充当左右指针的值,来规定区间范围,区间的边界都闭合,区间定义为[left,right]。一个int类型(mid)变量充当中间值的下标。
    区间应始终符合区间的定义,即区间大小应该大于等于1,也就是说左指针应该不大于右指针,即left <= right。
  • 存在多个target,需要找到这个target区间的左右边界:这题不同点在于target在数组中可能不止一个,相当于需要找到target的区间。我对于区间都是通过两个int类型,left、right变量充当左右指针的值,来规定区间范围,区间的边界都闭合,区间定义为[left,right],所以找区间就是需要找到左右两个边界。
    当找左边界的时候,把所有值为target的元素看作比target大的元素(实现思路就是把target == nums[mid]也当作target<nums[mid]处理),把target看作大于target的元素后数组此时中已经不包含target,模仿
    35. 搜索插入位置二分法找target,当最终不满足区间而跳出循环时,left指向的就是比target大的第一个元素,这里又已经把target看作大于target的值了,所以Left指向的也就是第一个target;
    当找右边界的时候,把所有值为target的元素看作比target小的元素(实现思路就是把target==nums[mid]也当作target>nums[mid]处理),把target看作小于target的元素后数组此时中已经不包含target,模仿
    35. 搜索插入位置二分法找target,当最终不满足区间而跳出循环时,right指向的就是比target小的最后一个元素,这里又已经把target看作小于target的值了,所以Left指向的也就是最后一个target的后一个元素(right指向的才是最后一个target);
  • 对于数组本身就不存在target特殊情况的处理,这里所指代的就是上述图例描述的情况二,总共有三种小分类。这种情况的共性就是因为不存在target,无论找左还是右边界,最终left、right都一致,又因为在35. 搜索插入位置此题中,我们已经发现当不满足区间定义跳出循环时满足left=right+1的特性,故当最终得到的右边界-左边界<0(准确的说是右边界-左边界 = -1,因为找左边界的时候取得是left指针,找右边界时取得是left - 1,且左右边界的left指针都只在同一个元素上,故右边界-左边界 = -1)。
    但对于数组中含有target的情况,此时target组成的区间满足区间定义,左右边界就是target元素组成的真实边界,此时右边界>=左边界
    所以可以对于target存在与否导致的右边界、左边界大小不同的特点,来对于数组本身就不存在target特殊情况进行处理。
  • 代码:
    Java
public int searchLeft(int[] nums,int target){
        int left = 0 ,right = nums.length - 1,mid = 0;
        while(left <= right){
            mid = left + ((right - left) >> 1);
            if(nums[mid] >= target){
                right = mid - 1;                
            }else{
                left = mid + 1;
            }
        }
        return left;
    }

    public int searchRight(int[] nums,int target){
        int left = 0 ,right = nums.length - 1,mid = 0;
        while(left <= right){
            mid = left + ((right - left) >> 1);
            if(nums[mid] <= target){
                left = mid + 1;
            }else{
                right = mid - 1;
            }
        }
        return left - 1;
    }

    public int[] searchRange(int[] nums, int target) {
        //左右闭
        if(nums.length == 0){
            return new int[]{-1,-1};
        }else if(searchRight(nums,target) - searchLeft(nums,target) < 0){
            return new int[]{-1,-1};
        }else{
            return new int[]{searchLeft(nums,target),searchRight(nums,target)};
        }

    }

C++

#include<stdio.h>
#include<vector>
using namespace std;
//此题中目标元素在数组中可能会出现多次,所以需要分别找到目标元素的左右边界 
//寻找边界可以使用二分查找法,找到数组中比目标元素大或小的第一个元素
//寻找*左边界*时,将target==nums[mid]的情况当作target<nums[mid]处理,只有这样才能一直往左边找
//寻找*右边界*时,将target==nums[mid]情况当做target>nums[mid]处理,只有这样才能一直往右边找

int searchLeftMargin(vector<int>& nums, int target){ //查找左边界 
	int left = 0,right = nums.size() - 1,mid;//定义target所存在的区间,以left、right为此区间的边界,并且边界左闭右闭 
	while(left <= right){//两边都是闭区间 [left,right],如[1,1]也符合区间定义,所以left=target 
		mid = left + (right - left) / 2;//left + (right - left) / 2这样防止溢出,等同于(left+right)/2 
		if(target > nums[mid]) { //目标值存在于原数组的右半部分,新区间为[mid+1,right],左边界右移left=mid+1
			left = mid + 1;
		}else{//此时条件为target <= nums[mid]
		//当target<nums[mid]时,目标值存在于原数组左半部分,新区间为[left,mid-1],右边界左移 
		//当target==num时,因为要找左边界,则尽可能往左半边界移动,把所有target看作大于target的元素 
		//这样当退出循环时,left就在从左数第一个target的位置上了(如果数组中存在target的话) 
			right = mid - 1;
		}
	} 
	//不符合区间定义时跳出循环,此时left就在从左数第一个target的位置上了(如果数组中存在target的话) 
	//不理解可以看自己博客关于“35-搜索插入位置二分查找”题目关于这个问题的详细说明 
	return left; 
}

int searchRightMargin(vector<int>& nums, int target){ //查找右边界 
	int left = 0,right = nums.size() - 1,mid;//定义target所存在的区间,以left、right为此区间的边界,并且边界左闭右闭 
	while(left <= right){//两边都是闭区间 [left,right],如[1,1]也符合区间定义,所以left=target 	
		mid = left + (right - left) / 2;//防止溢出,等同于mid = (left + right) / 2
		if(target >= nums[mid]){//当target>nums[mid]时,目标值存在于原数组右半部分,新区间为[mid+1,right],左边界右移 
		//当target==num时,因为要找右边界,则尽可能往右半边界移动,把所有target看作小于target的元素 
		//这样当退出循环时,right就在从左数最后一个target的位置上了(如果数组中存在target的话)
			left = mid + 1; 			
		}else{//当target<nums[mid]时,目标值存在于原数组左半部分,新区间为[left,mid-1],右边界左移 
			right = mid - 1;
		}
	}
	//不符合区间定义时跳出循环,此时right就在从左数最后一个target的位置上了(如果数组中存在target的话) 
	//此时left在大于最后一个target的第一个元素的位置上,而right比left小1,则right此时在最后一个target上 
	//不理解可以看自己博客关于“35-搜索插入位置二分查找”题目关于这个问题的详细说明 
	return right;
}

vector<int> searchRange(vector<int>& nums, int target) {//查找整体范围 
//一共有三种情况
//情况一,数组没有元素长度为0 
//情况二包含三种小分类,target大于所有元素或者target小于所有元素,数组中不存在target,如数组为{1,2,4},target=0或target=5 
//情况二的第三种分类是数组中不存在target,但若按升序插入target将查在数组的两元素之间,如数组为{1,2,4},target=3 
//情况三,数组中含有target元素,如{1,2,4},target=2 

	//情况一,数组没有元素,数组长度为0 
	if(nums.size() == 0) {
		return {-1,-1};
	}
	//情况二,此时数组中不含target,则无论找左边界还是右边界,每个方案产生的right和left边界指针都一样(因为不存在target==nums[mid]这种情况)
	//此时满足left=right+1,(不理解可以看自己博客关于“35-搜索插入位置二分查找”题目关于这个问题的详细说明)
	//因为searchRightMargin返回的是right,searchLeftMargin返回的是left,故searchRightMargin-searchLeftMargin<0 
	
	if (searchRightMargin(nums,target) - searchLeftMargin(nums,target) < 0){//说明找的元素大于或者小于全部元素
	//也就是此元素不存在于数组 返回[-1,-1] 
		return {-1,-1};
	}
	//情况三
	//若数组包含target,searchRightMargin-searchLeftMargin>=0(因为含有target,target形成的区间会满足区间定义,区间为左闭右闭,则right-left>=0)
	else{//此时说明元素存在与数组之中 
		return {searchLeftMargin(nums,target),searchRightMargin(nums,target)};
	}
}

4. 算法分析

n为数组长度
时间复杂度:O(logn)
空间复杂度:O(1)

5. 算法坑点

  • 存在多个target,需要找到这个target区间的左右边界:这题不同点在于target在数组中可能不止一个,相当于需要找到target的区间。我对于区间都是通过两个int类型,left、right变量充当左右指针的值,来规定区间范围,区间的边界都闭合,区间定义为[left,right],所以找区间就是需要找到左右两个边界。
    当找左边界的时候,把所有值为target的元素看作比target大的元素(实现思路就是把target == nums[mid]也当作target<nums[mid]处理),把target看作大于target的元素后数组此时中已经不包含target,模仿
    35. 搜索插入位置二分法找target,当最终不满足区间而跳出循环时,left指向的就是比target大的第一个元素,这里又已经把target看作大于target的值了,所以Left指向的也就是第一个target;
    当找右边界的时候,把所有值为target的元素看作比target小的元素(实现思路就是把target==nums[mid]也当作target>nums[mid]处理),把target看作小于target的元素后数组此时中已经不包含target,模仿
    35. 搜索插入位置二分法找target,当最终不满足区间而跳出循环时,right指向的就是比target小的最后一个元素,这里又已经把target看作小于target的值了,所以Left指向的也就是最后一个target;
  • 对于数组本身就不存在target特殊情况的处理,这里所指代的就是上述图例描述的情况二,总共有三种小分类。这种情况的共性就是因为不存在target,无论找左还是右边界,最终left、right都一致,又因为在35. 搜索插入位置此题中,我们以经发现当不满足区间定义跳出循环时满足left=right+1的特性,故此时左边界=右边界+1,(找左边界的时候取得是left指针,找右边界时取得是right指针)故左边界>右边界。
    但对于数组中含有target的情况,此时target组成的区间满足区间定义,左右边界就是target元素组成的真实边界,此时右边界>=左边界
    所以可以对于target存在与否导致的右边界、左边界大小不同的特点,来对于数组本身就不存在target特殊情况进行处理。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值