二分查找
搜索插入位置
题目
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 O(log n)
的算法。
解题思路
- 使用二分查找
- 如果找到目标值,则返回其索引
- 如果未找到目标值,则返回它将会被按顺序插入的位置
代码
const searchInsert = function (nums, target) {
let left = 0;
let right = nums.length - 1;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
if (nums[mid] === target) {
return mid;
} else if (nums[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
// 在二分查找失败后,left 就是“第一个大于或等于 target 的元素下标”,也就是插入位置
return left;
};
搜索旋转排序数组
题目
整数数组 nums
按升序排列,数组中的值 互不相同 。
在传递给函数之前,nums
在预先未知的某个下标 k
(0 <= k < nums.length
)上进行了 旋转,使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]]
(下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7]
在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2]
。
给你 旋转后 的数组 nums
和一个整数 target
,如果 nums
中存在这个目标值 target
,则返回它的下标,否则返回 -1
。
你必须设计一个时间复杂度为 O(log n)
的算法解决此问题。
解题思路
- 使用二分查找
- 相比普通二分查找,需要先判断哪一半是有序的
代码
/**
* @param {number[]} nums
* @param {number} target
* @return {number}
* @description 二分查找
*/
const search = function (nums, target) {
// 定义左右指针
let left = 0;
let right = nums.length - 1;
// 二分查找
while (left <= right) {
let mid = Math.floor((left + right) / 2);
if (nums[mid] === target) {
return mid;
}
/**
* 相比于普通的二分查找,多了一个关键步骤:判断哪一半是有序的
* 因为旋转数组的特点,所以有一半是有序的,另一半是无序的
*/
if (nums[left] <= nums[mid]) {
// 左边是有序的
// 判断 target 是否落在左半边的有序范围内
if (target >= nums[left] && target <= nums[mid]) {
// 如果 target 在左半边的有序范围内,则缩小右边界
right = mid - 1;
} else {
// 如果 target 不在左半边的有序范围内,则缩小左边界
left = mid + 1;
}
} else {
// 右边是有序的
// 判断 target 是否落在右半边的有序范围内
if (target >= nums[mid] && target <= nums[right]) {
// 如果 target 在右半边的有序范围内,则缩小左边界
left = mid + 1;
} else {
right = mid - 1;
}
}
}
return -1;
};
在排序数组中查找元素的第一个和最后一个位置
题目
给你一个按照非递减顺序排列的整数数组 nums
,和一个目标值 target
。请你找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target
,返回 [-1, -1]
。
你必须设计并实现时间复杂度为 O(log n)
的算法解决此问题。
解题思路
- 使用双二分查找
- 分别查找最左边的目标值和最右边的目标值
代码
/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
* @description 双二分查找
*/
const searchRange = function (nums, target) {
// 查找最左边的位置
function findLeft() {
let left = 0;
let right = nums.length - 1;
let index = -1;
while (left <= right) {
let mid = Math.floor((left + right) / 2);
if (nums[mid] >= target) {
right = mid - 1;
/**
* 记录「一个等于 target 的位置」
* 不立刻返回,因为可能还有更左边的 target
*/
if (nums[mid] === target) index = mid;
} else {
left = mid + 1;
}
}
return index;
}
// 查找最右边的位置
function findRight() {
let left = 0;
let right = nums.length - 1;
let index = -1;
while (left <= right) {
let mid = Math.floor((left + right) / 2);
if (nums[mid] <= target) {
left = mid + 1;
/**
* 记录「一个等于 target 的位置」
* 不立刻返回,因为可能还有更右边的 target
*/
if (nums[mid] === target) index = mid;
} else {
right = mid - 1;
}
}
return index;
}
return [findLeft(), findRight()];
};