启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第3天,点击查看活动详情
该题是数组二分查找题型第三题。
题目来源
34. 在排序数组中查找元素的第一个和最后一个位置(LeetCode)
题目描述(中等)
给你一个按照非递减顺序排列的整数数组 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]
提示
nums是一个非递减数组
题目解析
该题我们可以理解为寻找 target 在数组中的左右边界,共计三种情况:
target小于数组范围的左边(最小值)或者右边(最大值),例如数组为[3, 4, 5],target为2或者数组为[3, 4, 5],target为6,此时应该返回[-1, -1]。target在数组范围内,但数组不存在target,例如数组为[2, 3, 6],target为5,此时应该返回[-1, -1]。target在数组范围内,且数组存在target,例如数组为[1, 2, 3],target为1,此时应该返回[0, 0]。
二分查找左右边界
对二分查找接触不多的小伙伴,一开始就用一个二分循环查找左右边界,很容易混淆,可以先用两个二分循环分别查找左边界和右边界,最后对左右边界的值进行判断,返回最终结果。
代码(两个二分循环)
注:此时计算出来的左边界比 target 的左边界小 1 ,同理,右边界比 target 的右边界大 1 。
/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/
var searchRange = function(nums, target) {
let left = getLeft(nums, target),right = getRight(nums,target)
if(right - left < 2){
return [-1, -1]
}
return [left + 1, right - 1]
};
// 寻找左边界
var getLeft = function(nums,target) {
let left = 0,right = nums.length - 1
while (right >= left) {
let mid = Math.floor((left + right) / 2)
if(nums[mid] >= target) {
right = mid - 1
}else {
left = mid + 1
}
}
return right
}
// 寻找右边界
var getRight = function(nums,target) {
let left = 0,right = nums.length - 1
while(right >= left){
let mid = Math.floor((left + right) / 2)
if(nums[mid] <= target){
left = mid + 1
}else{
right = mid - 1
}
}
return left
}
如图:
代码(优化)
/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/
var searchRange = function(nums, target) {
let left = getTarget(nums, target, true),right = getTarget(nums, target, false)
if(right - left < 2){
return [-1, -1]
}
console.log(left,right)
return [left + 1, right - 1]
};
var getTarget = function(nums, target, area) {
let left = 0,right = nums.length - 1
while (right >= left) {
let mid = Math.floor((left + right) / 2)
if(nums[mid] > target) {
right = mid - 1
}else if (nums[mid] < target) {
left = mid + 1
}else {
area ? right = mid - 1 : left = mid + 1
}
}
return area ? right : left
}
如图:
- 时间复杂度:O(log n),其中 n 为数组的长度。二分查找的时间复杂度为 O(log n) ,一共会执行两次,因此总时间复杂度为 O(log n) 。
- 空间复杂度:O(1)。只需要常数空间存放若干变量。
二分查找
当数组存在 target 时,但不知道 target 的数量,此时只要通过二分查找找到下标 i 使得 nums[i] === target ,在以下标 i ,为起点分别向前后遍历直到出现不等于 target 的位置,停止遍历并返回。
/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/
var searchRange = function(nums, target) {
let left = 0,right = nums.length - 1
while (right >= left){
let mid = Math.floor((left + right)/2)
if (nums[mid] > target){
right = mid - 1
}else if (nums[mid] < target){
left = mid + 1
}else{
return [searchArea(nums, mid, target, -1), searchArea(nums, mid, target, 1)]
}
}
return [-1, -1]
};
var searchArea = function(nums, mid, target, num){
while (nums[mid] === target){
mid += num
}
return mid -= num
}
如图:
执行用时和内存消耗仅供参考,大家可以多提交几次。如有更好的想法,欢迎大家提出。