问题描述
给定一个按照升序排列的整数数组 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
的索引,我们分别记为leftIndex
和rightIndex
。
为了提高代码的复用程度,标答中构造了一个函数int binarySearch(vector<int>& nums, int target, bool lower)
。形参中的lower
代表我是想找leftIndex
还是rightIndex
(lower
为true
找leftIndex
,lower
为false
找rightIndex
)。
我们可以把代码拆开来看,如果我想找rightIndex
,即lower
为false
(注意一下这个函数返回的其实是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
,即lower
为true
,则代码如下:
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;
呃呃呃呃看不懂了有时间再看吧。。。