二分搜索是一种高效的搜索算法,可以在有序数组中查找特定元素。寻找左侧边界的二分搜索是一种变体,它可以找到第一个等于目标值的元素的位置。如果数组中不存在目标值,则返回-1。该算法的基本思想是在每次迭代中将搜索范围缩小一半,直到找到目标元素或搜索范围为空为止。在寻找左侧边界时,如果中间元素等于目标值,则继续在左半部分搜索,否则在右半部分搜索。如果中间元素大于目标值,则在左半部分搜索,否则在右半部分搜索。这样可以保证最终找到的位置是第一个等于目标值的元素的位置。
// 这种方式取的区间是[left, right),即左闭右开区间
function leftBound1(nums, target) {
if (nums.length === 0) {
return -1;
}
let left = 0;
let right = nums.length - 1;
// 此时终止条件式left == right
// 每次循环的搜索区间是 [left, right)
while (left < right) {
let mid = parseInt((left + right) / 2, 10);
if (nums[mid] === target) {
// 该算法能够搜索左边界的原因是其在遇到nums[mid] == target时不会立即返回,而是缩小区间的上届right,在区间[left,right)中继续搜索,即不断向左收缩,达到锁定左侧边界的目的
right = mid;
} else if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid;
}
}
// 如果target比所有数都大,此时返回的索引将会是nums.length则证明没找到,返回-1
if (left === nums.length) {
return -1;
}
// 如果找到的数比所有的都小,将会返回索引为0,此时需要进行判断索引为0时是否是target值
return nums[left] === target ? left : -1;
}
const nums = [1, 2, 2, 2, 3];
const target = 2;
console.log(leftBound1(nums, target));
// 这种方式取得区间是[left,right]
function leftBound2(nums, target) {
let left = 0;
let right = nums.length - 1;
while (left <= right) {
const mid = parseInt((left + right) / 2, 10);
if (nums[mid] === target) {
right = mid - 1;
} else if (nums[mid] > target) {
right = mid - 1;
} else if (nums[mid] < target) {
left = mid + 1;
}
}
if (left >= nums.length || nums[left] !== target) {
return -1;
}
return left;
}
const nums2 = [1, 2, 2, 2, 3];
const target2 = 2;
console.log(leftBound2(nums2, target2));
该算法的时间复杂度为O(log n),比线性搜索更快。然而,该算法需要数组是有序的,如果数组无序,则需要先进行排序,这会增加时间复杂度。此外,该算法只适用于静态数组,如果数组需要频繁插入或删除元素,则需要使用其他数据结构。