34. 在排序数组中查找元素的第一个和最后一个位置
链接:https://leetcode-cn.com/problems/find-first-and-last-position-of-element-in-sorted-array/
题目描述见链接内容。
解法1:暴力法
逐个匹配数组元素,用一个标识符来标识第一个匹配的位置和最后一个匹配的位置:
var searchRange = function (nums, target) {
let result = [-1, -1],
flag = false;
for (let i = 0, len = nums.length; i < len; i++) {
if (nums[i] === target) {
if (!flag) {
result[0] = i;
flag = true;
}
result[1] = i;
} else {
if (flag) {
result[1] = i - 1;
return result;
}
}
}
return result;
};
- 时间复杂度:
${O(N)}$
- 空间复杂度:
${O(1)}$
- 执行结果:执行用时64ms,在所有JavaScript提交中击败了97%的用户,内存消耗:39.3MB,在所有JavaScript提交中击败了46%的用户
解法2:伪·二分法
因为是有序数组,并且需要查找元素,自然可以想到二分法,但是我这个是一个伪二分法,指的是利用二分法不全面,无法做到${O(log N)}$
的时间复杂度。
我的思路是,利用二分法找到一个等于target
的下标,然后从这个下标开始,向左右扩展,直到左右元素与target
不等或者越界
var searchRange = function (nums, target) {
let result = [-1, -1],
left = 0,
right = nums.length,
base = -1;
// 使用二分法找到与 target 相等的下标
while (left <= right) {
const mid = ~~((left + right) / 2);
if (nums[mid] === target) {
base = mid;
break;
} else {
if (nums[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
}
// 如果找到的话,向左右扩展
if (base > -1) {
result = [base, base];
let start = base - 1,
end = base + 1;
while (start >= 0 || end <= nums.length - 1) {
if (nums[start] === nums[base]) {
result[0] = start;
start -= 1;
} else {
start = -1;
}
if (nums[end] === nums[base]) {
result[1] = end;
end += 1;
} else {
end = nums.length;
}
}
}
return result;
};
- 时间复杂度:
${O(N * logN)}$
,最坏的情况下在二分查找之后还要再完整遍历一遍数组 - 空间复杂度:
${O(1)}$
- 执行结果:执行用时72ms,在所有JavaScript提交中击败了86%的用户,内存消耗:39.2MB,在所有JavaScript提交中击败了66%的用户
解法3:真·二分法
实际上,第一个位置就是第一个大于等于target
的位置,第二个位置就是第一个大于target
的位置减一,直接利用二分法查找两边即可
用习惯的二分法套路:
function binarySearch(nums, target, equal) {
let left = 0,
right = nums.length;
while (left < right) {
const mid = ~~((left + right) / 2);
if (nums[mid] < target || (equal && nums[mid] <= target)) {
left = mid + 1;
} else {
right = mid;
}
}
return left;
}
要注意的:
- 循环终止的条件不包含等号,这样结束循环时,
left
与right
是相等的,任意返回一个就可以 if
中的逻辑判断条件,是与我们的目的相反的,要找到第一个大于等于target
的位置,if
中就取反,找小于target
的下标- 因为
mid
是向下取整,所以left
迭代时需要+1
,而right
向下取则不需要-1
- 利用
equal
来复用代码,equal
为true
时,用来寻找第一个大于target
的位置
完整代码:
function binarySearch(nums, target, equal) {
let left = 0,
right = nums.length;
while (left < right) {
const mid = ~~((left + right) / 2);
if (nums[mid] < target || (equal && nums[mid] <= target)) {
left = mid + 1;
} else {
right = mid;
}
}
return left;
}
var searchRange = function (nums, target) {
// 找到第一个大于等于 target 的数字
const start = binarySearch(nums, target),
// 找到第一个大于 target 的数字
end = binarySearch(nums, target, true) - 1;
if (start > -1 && end < nums.length && nums[start] === target && nums[start] === nums[end]) {
return [start, end];
}
return [-1, -1];
};
- 时间复杂度:
${O(logN)}$
- 空间复杂度:
${O(1)}$
- 执行结果:执行用时72ms,在所有JavaScript提交中击败了86%的用户,内存消耗:39.3MB,在所有JavaScript提交中击败了46%的用户