前言
大家好,我是跟詹姆斯·邦德扮演者同名同颜的肖恩(Sean)
持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第6天
前端算法救赎系列是我避免变成一个只会前端的前端的第一步,我希望把自己学习过程中的心得分享出来,也欢迎大家指出不足,共同进步
二分查找(Binary Search)是我进入算法领域的第一站,这个系列就从这道最简单最质朴,面试必会的二分查找开始。
背景
面试官: 给定一个升序数组 nums,数组里只包含整数,在该数组中找到指定值 target,返回其下标值,如果没找到则返回-1
这还不简单?直接 for 循环遍历啊
面试官:哈哈,你真聪明,回去等消息吧
概念
二分查找是什么
如字面意思,将一组数据从中间分成两部分,如果中间的数值比目标值小,则对右边那部分进行二分查找搜索目标值;如果中间的数值比目标值大,则对左边的那部分进行二分查找。当然,这么做的前提是这组数据是升序的。
为什么 for 循环不能让面试官满意
- for 循环是步进式的搜索,如果从头部开始搜索,搜索元素在尾部,那么这组数据有多长,我们就要查多少次,它的时间复杂度为
O(n) - 而二分搜索利用排序好的数组,每一次查找都将搜索范围减半,如果搜索一个长度为 n 的数组,最坏的情况下是将数组对半到只剩一个元素才找到,即
log2n次,时间复杂度为O(logn),是低于线性的 for 循环的O(n)的
如何进行二分搜索
两根指针
两根指针分别指向数组头部和尾部两个位置
let start = 0;
let end = nums.length;
循环移动指针求两根指针的中点
while 循环退出的条件是 start + 1 >= end,注意好边界问题。
因为数组已经排序过了,中点位置的值正好可以作为划分低位区和高位区的界限。
while (start + 1 < end) {
const mid = start + Math.floor((end - start) / 2);
}
对比中点值和 target 大小缩小区间
如果 target 值比中间元素值大,则 target 一定落在高位区,只需要把头指针移到中间位置就好了,再循环求中点对比大小。target 值比中间元素小则移动尾部指针。
while (start + 1 < end) {
const mid = start + Math.floor((end - start) / 2);
if (nums[mid] < target) {
start = mid;
} else {
right = mid;
}
}
判断值所在位置
最后不要忘记,找到两根指针哪一个是 target 元素,返回指针数值,即是 target 元素所在下标。
如果两个都不是,则一定没有该元素,返回-1 就好。
if (nums[start] === target) return start;
if (nums[end] === target) return end;
return -1;
最终代码
function binarySearch(nums, target) {
if (!nums?.length) return -1;
let start = 0;
let end = nums.length;
while (start + 1 < end) {
const mid = start + Math.floor((end - start) / 2);
if (nums[mid] < target) {
start = mid;
} else {
right = mid;
}
}
if (nums[start] === target) return start;
if (nums[end] === target) return end;
return -1;
}
结语
是不是很简单呢?该二分查找模板可能没有其他写法简洁,但是它可以在其他二分查找类问题通杀运用,做到一套模板,解一类题。后面介绍更多二分查找类问题的时候,你会发现这个模板的强大。