Leetcode34. Find First and Last Position of Element in Sorted Array | Binary Search

1.题意

给定一个可能有重复元素的非递减数列,要求找出给定元素第一次出现和最后一次出现的位置(区间),若不存在则返回{-1, -1}。注意:
a.该数列可以为空
b.对只出现一次的情况,返回{pos, pos}

2.算法

二分

3.分析

以前做二分都是查找单个元素,现在是找区间。我们可以把找区间转化为找两个端点,分别进行一次二分查找。

3.1 使用二分查找来寻找左端点的情况

3.1.1 每轮子区间的选择
33的分析里写了很长一段来说明子区间的选择,是因为那个数列不单调。这里它不递减我们就可以视为单调,直接应用结论就好(原理在33那里分析过了)。

if(nums[mid] > target)
    right = mid-1;
if(nums[mid < target)
    right = mid+1;

=的情况属于对mid的判断的部分。
3.1.2 对mid的判断
做这道题最开始分析的困难点就是,当nums[mid]==target时,实际可能有三种情况:
a.mid是左端点
b.mid是右端点
c.mid是中间部分(目标元素出现3次及以上的情况)
思考的时候很容易被c情况扰乱,觉得这种情况很难处理是往左还是往右。但一旦我们确定了策略是找左右端点,c情况就完全不会对我们造成任何困扰,我们只专注于mid是否符合端点的条件(ab的条件比c好找多了)。
现在我们找的是左端点,由于这是一个非递减数列,所以左端点lans的条件是:

num[lans]>num[lans-1] && num[lans] == target

这里有一个惯例的边界情况要考虑:目标点有没有可能出现在数组的边界? 以这里为例子就是,左端点有没有可能是数组第一个元素?是有可能的,而如果是这种情况,上面就会数组越界,所以在它之前加一句特判:

!lans && num[lans] == target

但是我们每轮的操作如果到此为止,是一定会进入死循环的。因为这里我们还没有对c情况进行操作!这里我们的目标是找到左端点,而如果上面两条对mid的判断都不符合,就说明我们位于目标区间的中部,即左端点在我们的左边,所以向左挪动。

else
    right = mid-1;

看起来好像没什么,但其实一开始思考的时候,如何对c情况操作我想了很久很久都没想出来。之后意识到这个问题实际就是分别二分找左右端点之后,c情况一下子就迎刃而解了。

3.2 对右区间的查找

思路和做法与左区间是完全一致的,唯一不同就是条件设置相反,注意:
1.需要特判一下右端点是数组最后一个元素的情况。33题不需要特判右边是题目特点决定的,实际上做的时候两边端点的特殊情况都要考虑。
2.如果你两次二分共用了left, right变量,记得重新初始化。

4.代码
class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        if (nums.size() == 0)
            return {-1, -1};
        
        int left = 0, right = nums.size()-1, mid = -1, lans = -1, rans = -1;
        
        while(left <= right){
            mid = left + (right-left)/2;
            if(!mid && nums[mid]==target){
                lans = mid;
                break;
            }
            if(nums[mid]==target && nums[mid-1]<nums[mid]){
                lans = mid;
                break;
            }
            else if(nums[mid] > target)
                right = mid-1;
            else if(nums[mid] < target)
                left = mid+1;
            else
                right = mid-1;
        }
        
        
        left = 0, right = nums.size()-1, mid = -1;
        while(left <= right){
            mid = left + (right-left)/2;
            if(mid==nums.size()-1 && nums[mid]==target){
                rans = mid;
                break;
            }
            if(nums[mid]==target && nums[mid+1]>nums[mid]){
                rans = mid;
                break;
            }
            else if(nums[mid] > target)
                right = mid-1;
            else if(nums[mid] < target)
                left = mid+1;
            else
                left = mid+1;
        }
        
        if(rans == -1 && lans == -1)
            return {-1, -1};
        return {lans, rans};
        
    }
};
5.复杂度

时间复杂度O(logn)
空间复杂度O(1)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值