今天是十五周算法训练营的第二周,主要讲二分搜索,包含:二分搜索一个数、寻找左侧边界的二分搜索、寻找右侧边界的二分搜索。(欢迎加入十五周算法训练营,与小伙伴一起卷算法)
/**
* 寻找一个数(基本的二分搜索)
*/
function binarySearch(nums, target) {
let left = 0;
// 此处赋值为最后的索引,再结合while判断条件,搜索区间是个闭区间,为[left, right]
let right = nums.length - 1;
// 此处比较符号是<=,这是因为若为<,则终止的时候会有一个数,该数没有做比较,会出问题
// 此时终止条件是left == right + 1
while (left <= right) {
// let mid = left + parseInt((right - left) / 2, 10);
let mid = parseInt((left + right) / 2, 10);
if (nums[mid] === target) {
return mid;
} else if (nums[mid] > target) {
right = mid - 1;
} else if (nums[mid] < target) {
left = mid + 1;
}
}
return -1;
}
const nums = [1, 2, 3, 4, 5];
const target = 4;
console.log(binarySearch(nums, target));
/**
* 寻找左侧边界的二分搜索
*/
// 这种方式取的区间是[left, right),即左闭右开区间
function leftBound1(nums, target) {
if (nums.length === 0) {
return -1;
}
let left = 0;
let right = nums.length;
// 此时终止条件是left == right
// 每次循环的搜索区间是[left, right)
while (left < right) {
let mid = parseInt((left + right) / 2, 10);
if (nums[mid] === target) {
// 该算法能够搜索左边界的原因是其在遇到nums[mid] == target时不会立即返回,而是缩小搜索区间的上界right,在区间[left, mid)中继续搜索,即不断向左收缩,达到锁定左侧边界的目的
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));
/**
* 寻找右侧边界的二分查找
*/
// 这种方式取的区间是[left, right)
function rightBound1(nums, target) {
if (nums.length === 0) {
return -1;
}
let left = 0;
let right = nums.length;
while (left < right) {
const mid = parseInt((left + right) / 2, 10);
if (nums[mid] === target) {
left = mid + 1;
} else if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid;
}
}
return left - 1;
}
const nums1 = [1, 2, 2, 2, 3];
const target1 = 2;
console.log(rightBound1(nums1, target1));
// 这种方式取的区间是[left, right]
function rightBound2(nums, target) {
let left = 0;
right = nums.length - 1;
while (left <= right) {
const mid = parseInt((left + right) / 2, 10);
if (nums[mid] === target) {
left = mid + 1;
} else if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid - 1;
}
}
if (right < 0 || nums[right] !== target) {
return -1;
}
return right;
}
const nums2 = [1, 2, 2, 2, 3];
const target2 = 2;
console.log(rightBound2(nums2, target2));