前端算法救赎———面试必会二分查找

126 阅读3分钟

前言

大家好,我是跟詹姆斯·邦德扮演者同名同颜的肖恩(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;
}

结语

是不是很简单呢?该二分查找模板可能没有其他写法简洁,但是它可以在其他二分查找类问题通杀运用,做到一套模板,解一类题。后面介绍更多二分查找类问题的时候,你会发现这个模板的强大。