34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode)
发布:2021年10月2日19:05:59
问题描述及示例
给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]。
进阶:
- 你可以设计并实现时间复杂度为 O(log n) 的算法解决此问题吗?
示例 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 <= 109
-109 <= nums[i] <= 109
nums 是一个非递减数组
-109 <= target <= 109
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/find-first-and-last-position-of-element-in-sorted-array
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
我的题解
我的题解1(indexOf和lastIndexOf)
既然是用JavaScript来做,那么自然最容易想到的就是这种方法了,一试,居然还真可以~🤣,而且性能表现也不是想象中那么拉胯,具体就不多解释了, 怕招打……
/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/
var searchRange = function(nums, target) {
return [nums.indexOf(target), nums.lastIndexOf(target)];
};
提交记录
88 / 88 个通过测试用例
状态:通过
执行用时:76 ms, 在所有 JavaScript 提交中击败了46.00%的用户
内存消耗:39 MB, 在所有 JavaScript 提交中击败了84.75%的用户
时间:2021/10/02 19:12
我的题解2(暴力解法)
这种解法也比较好理解。第一个 while 循环用于寻找 target 在数组中首次出现的位置,第二个 while 循环用于寻找 target 在数组中最后一次出现的位置。
/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/
var searchRange = function(nums, target) {
let index = 0;
let result = [-1, -1];
// 第一个 `while` 循环用于寻找 `target` 在数组中首次出现的位置
while(index < nums.length) {
if(nums[index] === target) {
result[0] = index;
// 一旦找到target,则立马用break结束这个while循环
break;
}
index++;
}
// 不要忘记重置index的值
index = nums.length - 1;
// 第二个 `while` 循环用于寻找 `target` 在数组中最后一次出现的位置
// 注意这里的结束条件不是简单的写成了index >= 0,因为result[1]一定不比result[0]小
while(index >= result[0]) {
if(nums[index] === target) {
result[1] = index;
// 一旦找到target,则立马用break结束这个while循环
break;
}
index--;
}
return result;
};
提交记录
88 / 88 个通过测试用例
状态:通过
执行用时:72 ms, 在所有 JavaScript 提交中击败了64.86%的用户
内存消耗:39.1 MB, 在所有 JavaScript 提交中击败了69.58%的用户
时间:2021/10/02 19:39
我的题解3(二分查找)
上面的两种解法中都没有利用【 nums是按照升序排列的】这一题目特点。而根据这一特点,我们可以利用二分查找的思路来搜索目标。
之前做过一个二分查找的题目:
本题的二分查找核心思路和上面的纯二分查找一样。唯一不同的是本题在找到 target 之后,还要分别进一步用二分思想来查找 target 区间的左边界和右边界。

注意,
left和right以及mid指针都是可重复利用的。
本题的思路大体可以分为三部分:
- 利用
front和back以及mid指针遵循常规的二分查找思路搜寻target,并用mid指针指向搜寻到的target。(对应最外层的while循环) - 找到
target后,保存front和back指针的状态,并利用left和right以及mid指针来在【1】中所找到的target左半部分继续遵循二分查找思路搜寻左边界。(对应内层的第一个while循环) - 找到左边界后,再利用之前保存的
front和back指针的状态继续按照【2】的套路搜寻右边界。(对应内层的第二个while循环)
其中三个部分中的二分查找结束条件都有细微的不同。
二分查找的思路可以查看上面的博客。本题的相关详解请看下方注释:
/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/
var searchRange = function(nums, target) {
// front和back和纯二分查找中的作用一样
let front = 0;
let back = nums.length - 1;
// left和right可以看做是两个临时的辅助指针,用于确定左边界和右边界,
// 其初始值无所谓是什么,因为之后在被用之前会被覆盖,他们都可以被重复利用
let left = 0;
let right = 0;
// mid用于指示一段区间的中间位置,它也可以重复利用
let mid = 0;
// result用于存储最后结果,初始化为[-1,-1]
let result = [-1, -1];
// 最外层while循环用于初步定位target,但是无法准确确定左边界和右边界,
// 这里是【步骤1】的部分
while(front <= back) {
mid = Math.floor((back - front) / 2 + front);
// 下面的if判断中的逻辑就是找到target后,继续搜寻左边界和右边界的逻辑(即步骤2和3)
if(nums[mid] === target) {
// 先搜寻左边界,这里是【步骤2】的部分
left = front;
right = mid;
while(!(nums[mid] === target && nums[mid - 1] !== target)) {
// 【tag1】注意这里要用Math.floor,而不能用Math.ceil,详情请看下方【补充1】
mid = Math.floor((right - left) / 2 + left);
left = nums[mid] === target ? left : mid;
right = nums[mid] === target ? mid : right;
}
// 上面的while循环结束后,说明已经找到了左边界,且就是mid所指之处
result[0] = mid;
// 左边界找到后,恢复mid指针的指向,并重新初始化left、right指针的指向开始搜寻右边界
// 这里是【步骤3】的部分
mid = Math.floor((back - front) / 2 + front);
left = mid;
right = back;
while(!(nums[mid] === target && nums[mid + 1] !== target)) {
// 【tag2】注意这里要用Math.ceil,而不能用Math.floor,详情请看下方【补充1】
mid = Math.ceil((right - left) / 2 + left);
left = nums[mid] === target ? mid : left;
right = nums[mid] === target ? right : mid;
}
// 上面的while循环结束后,说明已经找到了右边界,且就是mid所指之处
result[1] = mid;
// 找到左右边界后,最外层的while循环也应该停止了,所以此处应直接break
break;
}
// 这里是【步骤1】的部分
front = nums[mid] < target ? mid + 1 : front;
back = nums[mid] < target ? back : mid - 1;
}
return result;
};
提交记录
88 / 88 个通过测试用例
状态:通过
执行用时:68 ms, 在所有 JavaScript 提交中击败了81.37%的用户
内存消耗:39.6 MB, 在所有 JavaScript 提交中击败了22.12%的用户
时间:2021/10/02 19:39
可以看到,这种做法的时间表现还是好了一点的,但是空间表现就不如前面的两种解法了,毕竟用到了好几个辅助变量。而且这种写法在理解上的复杂程度也更高了。
【补充1】
【tag1】处要使用 Math.floor 是为了应对 mid 指针和 right 指针重合的情况,如果使用 Math.ceil 的话就会在步骤【2】陷入死循环。(比如:nums = [8,8,9])
同理, 【tag2】处要使用 Math.ceil 是为了应对 mid 指针和 left 指针重合的情况,如果使用 Math.floor 的话就会在步骤【3】陷入死循环。(比如:nums = [7,8,8])
具体过程可以自行在浏览器的开发者工具中观察。
官方题解
更新:2021年7月29日18:43:21
因为我考虑到著作权归属问题,所以【官方题解】部分我不再粘贴具体的代码了,可到下方的链接中查看。
更新:2021年10月2日20:42:41
参考:在排序数组中查找元素的第一个和最后一个位置 - 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode)
【更新结束】
有关参考
更新:2021年10月2日20:41:24
参考:【算法-LeetCode】704. 二分查找_赖念安的博客-CSDN博客
&spm=1001.2101.3001.5002&articleId=120588243&d=1&t=3&u=d2a2592812fa4cc4b86f3d47c71d56a6)
250

被折叠的 条评论
为什么被折叠?



