【LeetCode】34. 在排序数组中查找元素的第一个和最后一个位置

问题描述

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

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

进阶:你可以设计并实现时间复杂度为 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]

提示:

  • 0 <= nums.length <= 105
  • -109 <= nums[i] <= 109
  • nums 是一个非递减数组
  • -109 <= target <= 109

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/find-first-and-last-position-of-element-in-sorted-array

类二分法(我做的)

呃 回过头来看这就是一道很简单的运用二分法求解的题目,也不知道自己咋就卡了那么久……
那么先看看我写的代码吧(和标答比起来还是不够简洁,复用性也不太高)

class Solution {
public:
	vector<int> searchRange(vector<int>& nums, int target) {
		int left = 0;
		int right = nums.size() - 1;
		int mid;
		int first;
		int last;
		while (left <= right) {
			mid = (left + right) / 2;
			if (nums[mid] == target) {//求两个边界:first和last
				break;
			}
			else if (nums[mid] < target) {
				left = mid + 1;
			}
			else {
				right = mid - 1;
			}
		}
		if (left > right) {
			return { -1,-1 };
		}
		else {//nums[mid] == target
			int l = left;
			int r = mid;
			//求左边界first
			while (l <= r) {
				int m = (l + r) / 2;
				if (nums[m] == target) {
					first = m;
					r = m - 1;
				}
				else {
					l = m + 1;
				}
			}
			//求右边界last
			l = mid;
			r = right;
			while (l <= r) {
				int m = (l + r) / 2;
				if (nums[m] == target) {
					last = m;
					l = m + 1;
				}
				else {
					r = m - 1;
				}
			}
			return { first,last };
		}
	}
};

运行结果在这里::
在这里插入图片描述
我来写一写我的思路吧:

首先像二分法基础版一样,不算循环mid=(left+right)/2。对于正常的二分法,当nums[mid]==target的时候就直接返回了(或者循环条件不成立退出循环,找不到target);在这里,我们找到nums[mid]==target之后直接break跳出循环,之后把这个数组一分为二,数组边界分别为:1.[left,mid] 2.[mid,right]。之后只需要在这两个数组中找元素为target的边界即可,我们以拆分出来的第一个数组为例讲解,依然应用了二分法。

在这个数组中,数组的后半部分的元素值全是target,我们需要找到的是最前面是target的那个索引。依然用二分法解决,这里再贴一下代码吧。

int l = left;
int r = mid;
//求左边界first
while (l <= r) {
	int m = (l + r) / 2;
	if (nums[m] == target) {
		first = m;
		r = m - 1;
	}
	else {
		l = m + 1;
	}
}

注意一下,在所有的二分法求解时,更新right或者left的时候,都需要在mid的基础上+1或者-1,这样循环条件恰好是left<=right,否则循环条件会很麻烦。

这里也不例外,但是我们需要注意的就是如果r=m-1了,那么此时的查询范围里面有可能没有target了,所以我们在每次更新r时要提前记录m的值为first,这样first的最后一次赋值就是我们想要找的边界(最后一定会因为循环条件不满足而退出循环)。

对于拆分出来的第二个数组也不例外,用同样的方法就能解决。

二分法(标答)

class Solution { 
public:
    int binarySearch(vector<int>& nums, int target, bool lower) {
        int left = 0, right = (int)nums.size() - 1, ans = (int)nums.size();
        while (left <= right) {
            int mid = (left + right) / 2;
            if (nums[mid] > target || (lower && nums[mid] >= target)) {
                right = mid - 1;
                ans = mid;
            } else {
                left = mid + 1;
            }
        }
        return ans;
    }

    vector<int> searchRange(vector<int>& nums, int target) {
        int leftIdx = binarySearch(nums, target, true);
        int rightIdx = binarySearch(nums, target, false) - 1;
        if (leftIdx <= rightIdx && rightIdx < nums.size() && nums[leftIdx] == target && nums[rightIdx] == target) {
            return vector<int>{leftIdx, rightIdx};
        } 
        return {-1, -1};
    }
};

看了看标答,代码还是很简单的(复用性比较高)。并且,我的代码里面用了3次二分法,而标答只用了两次二分法,时间复杂度应该会稍微低一点。我们来看看标答是怎么写的。

这个题目想找到的是最先出现的target的索引和最后出现的target的索引,我们分别记为leftIndexrightIndex

为了提高代码的复用程度,标答中构造了一个函数int binarySearch(vector<int>& nums, int target, bool lower)。形参中的lower代表我是想找leftIndex还是rightIndexlowertrueleftIndexlowerfalserightIndex)。

我们可以把代码拆开来看,如果我想找rightIndex,即lowerfalse(注意一下这个函数返回的其实是rightIndex+1):

while (left <= right) {
    int mid = (left + right) / 2;
    if (nums[mid] > target) {
        right = mid - 1;
        ans = mid;
    } else {//nums[mid]<=target
        left = mid + 1;
    }
}
return ans;

想一想和正常二分法的区别:就在于如果nums[mid]==target时该如何做——因为是找rightIndex,所以我要找的可能是这个mid或者在mid的右边,因此我令left=mid+1

第二种情况,如果想找leftIndex,即lowertrue,则代码如下:

while (left <= right) {
    int mid = (left + right) / 2;
    if (nums[mid] >= target) {
        right = mid - 1;
        ans = mid;
    } else {//nums[mid]<target
        left = mid + 1;
    }
}
return ans;

呃呃呃呃看不懂了有时间再看吧。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值