C++每日一题:在排序数组中查找元素的第一个和最后一个位置

题目: 给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1],OJ链接

二分查找的三种写法

二分查找通常有以下几种写法,区别主要在于 while 里面:

形式 结论与建议
while (left <= right) 简单问题用,在循环体里能找到答案以后退出。
while (left < right) 复杂问题用,把答案留到退出循环以后,再判断。是解决二分问题的利器,尤其在边界问题用,这种方式考虑细节最少,但是需要一定练习才能灵活运用。
while (left + 1 < right) 不建议,本质上和 while (left <= right) 写法一样,盲目套这个所谓的最无脑模板,反而学不会二分

LeetCode 第 34 题采用 while(left <= right)
本篇题解采用 while(left <= right) 这种二分查找的写法,并分析如何在循环体里设置 left 和 right,和应该返回 left 和 right,即怎样思考边界问题。只要逻辑是完备,并且足够细心,写对二分查找问题并不困难。

第 1 部分:查找 target 出现的第 1 个位置

二分查找的基本用法是在一个有序数组里查找目标元素,具体是看区间中间元素的值 nums[mid] 与 target 的大小关系。

  • 如果等于,就可以直接返回;
  • 如果严格大于,就往右边查找;
  • 如果严格小于,就往左边查找。
  1. 如果当前看到的元素 恰好等于 target,那么当前元素有可能是 target 出现的第 11 个位置,由于我们要找第 11 个位置,此时我们应该向左边继续查找;
  2. 如果当前看到的元素 严格大于 target,那么当前元素一定不是要找的 target 出现的第 11 个位置,第 11 个位置肯定出现在 mid 的 左边 ,因此就需要在 [left, mid] 区间里继续查找;
  3. 如果当前看到的元素 严格小于 target,那么当前元素一定不是要找的 target 出现的第 11 个位置,第 11 个位置肯定出现在 mid 的 右边 ,因此就需要在 [mid + 1, right] 区间里继续查找。

代码 1:

private int findFirstPosition(int[] nums, int target) {
   
    int left = 0;
    int right = nums.length - 1;

    while (left <= right) {
   
        int mid = left + (right - left) / 2;

        if (nums[mid] == target) {
   
            // ① 不可以直接返回,应该继续向左边找,即 [left..mid - 1] 区间里找
            right = mid - 1;
        } else if (nums[mid] < target) {
   
            // 应该继续向右边找,即 [mid + 1, right] 区间里找
            left = mid + 1;
        } else {
   
            // 此时 nums[mid] > target,应该继续向左边找,即 [left..mid - 1] 区间里找
            right = mid - 1;
        }
    }

    // 此时 left 和 right 的位置关系是 [right, left],注意上面的 ①,此时 left 才是第 1 次元素出现的位置
    // 因此还需要特别做一次判断
    if (left != nums.length && nums[left] == target) {
   
        return left;
    }
    return -1;
}

解释:

  1. 第 11 次出现的位置和最后 11 次出现的位置肯定都在数组里。因此,初始化的时候 left = 0 、 right = nums.length - 1;
  2. nums[mid] == target 的时候,在 [left, mid - 1] 区间里找,有没有可能 nums[mid] 就是第 11 次出现的位置,有可能,但不要紧,退出循环的时候 right 指针在左,left 在右。如果数组里存在 target,那么 left 一定位于 target 出现的第 11 个位置,请看下图。
    在这里插入图片描述
    还有一种特殊情况,当要查找的目标元素不存在的时,分两种情况:(1)target 很大,(2)target 很小:
    target 很大,还是上面的例子:
    在这里插入图片描述
    target 很小,还是上面的例子:
    在这里插入图片描述
    以上特殊例子,解释了为什么在 while (left <= right) 退出循环以后,需要单独判断 left 是否越界,以及判断 nums[left] 是不是目标元素的原因。

第 2 部分:查找 target 出现的最后 1 个位置

可以直接看注释。代码 2:

private int findLastPosition(int[] nums, int target) {
   
    int left = 0;
    int right = nums.length - 1;

    while (left <= right) {
   
        int mid = left + (right - left) / 2;

        if (nums[mid] == target) {
   
            // 只有这里不一样:不可以直接返回,应该继续向右边找,即 [mid + 1, right] 区间里找
            left = mid + 1;
        } else if (nums[mid] < target) {
   
            // 应该继续向右边找,即 [mid + 1, right] 区间里找
            left = mid + 1;
        } <
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值