JavaScript 二分查找核心场景与边界处理详解

作者:拾光

时间:2026-10-26

浏览:660

本文详解 JavaScript 中二分查找的三种核心场景:查找特定数值、寻找左侧边界及右侧边界。通过对比不同搜索区间(左闭右开与左闭右闭)的实现方式,深入分析 mid 计算防溢出技巧及边界收缩逻辑,帮助开发者在算法面试中准确手写二分查找代码。

文章目录

你是否遇到过这样的刷题经历:起初面对题目毫无头绪,匆忙查阅题解后恍然大悟,随即按思路开始作答。然而中途卡壳,再次回顾题解,最终勉强完成。回头看实现代码,一步步拆解发现其实并不复杂。

然而某天面试时,考官恰好问及你曾做过的题目,此时你仅模糊记得做过,具体思路却已遗忘殆尽。

我也常陷于此种困境,自认算法基础薄弱,因此决定以文章形式记录学习算法的历程,作为一种输出。有人言“学习算法如同解数学题,记住公式,识别题型后套用即可”,那便以此法尝试。

二分查找是算法学习中的关键部分,且在面试中常要求手写实现。

本文探讨三个最常用的二分查找场景:查找一个特定数值、查找左侧边界、查找右侧边界。

二分查找公式

function binarySearch(nums, target) {
	let left = 0
	let right = ...
	while(...) {
		const mid = Math.floor(left + (right - left) / 2)
		if(nums[mid] === target) {
			...
		} else if(nums[mid] < target) {
			left = ...
		} else if(nums[mid] > target) {
			right = ...
		}
	} 

	return ...
}

分析二分查找技巧时,不应出现 else,而应将所有情况用 else if 清晰表述,以展示所有细节。

Math.floor(left + (right - left) / 2) 其实与 Math.floor((left +right)/2) 的结果一致。但当 left 和 right 数值较大时,直接相加可能导致溢出。使用 Math.floor(left + (right - left) / 2) 可有效防止此类溢出问题。

寻找一个数

var search = function (nums, target) {
  let left = 0;
  let right = nums.length - 1;
  while (left <= right) {
    const mid = Math.floor(left + (right - left) / 2);
    if (nums[mid] === target) {
      return mid;
    } else if (nums[mid] > target) {
      right = mid - 1;
    } else if (nums[mid] < target) {
      left = mid + 1;
    }
  }
  return -1;
};

力扣第 704 题二分查找,这是二分查找最简单的题型,绝大多数二分查找题型均基于此拓展。

首先需确定搜索区间。因定义的 right 为 nums.length - 1,故搜索区间为 [left, right],两端均闭合。当找到目标元素时,停止搜索并退出循环,返回目标值对应的索引。

若未找到目标元素,当循环终止条件 left === right + 1 满足时,直接返回 -1 即可。

缺陷

若给定有序数组 nums = [1,2,2,2,3],target 为 2,上述方法返回的索引是 2。若想获取 target 在 nums 中最左边或最右边满足条件的值,此方法便存在局限。

有人或许想到找到 target 的值后,向左或向右进行线性搜索。但这将难以保证二分查找对数级的时间复杂度。

寻找最左边满足条件的值

方式一

function leftBound(nums, target) {
  let left = 0;
  let right = nums.length;

  while (left < right) {
    const mid = Math.floor(left + (right - left) / 2);
    if (nums[mid] === target) {
      right = mid;
    } else if (nums[mid] < target) {
      left = mid + 1;
    } else if (nums[mid] > target) {
      right = mid;
    }
  }

  if (left === num.length) return -1;
  return nums[left] === target ? left : -1;
}

上述是一种常见代码形式,但与初始框架匹配。此处 while 中使用 < 而非 <=,因定义 right 时为 nums.length 而非 nums.length-1,说明搜索区间为 [left,right),左闭右开。故终止条件为 left == right 时。

另有一个不同点,使用 right = mid 而非 right = mid - 1,这受搜索区间影响。因搜索区间为 [left,right) 左闭右开,当 nums[mid] 被检测到时,下一步应缩小搜索区间。若 nums[mid] === target,虽已找到 target 的值,但不立即返回,而是将搜索区间缩小为 [left, mid)。随后不断向左收缩,直至锁定左侧边界,即当 left == right 时。

最后考虑越界情况,若 left 的值为 nums.length,说明查找左侧边界已超出搜索区间,即 target 的值比所有数都大。若 left 的值为 target,说明找到后直接返回。此时返回 left 与返回 right 等效,因终止条件为 left == right。

方式二

function leftBound(nums, target) {
  let left = 0;
  let right = nums.length - 1;
  while (left <= right) {
    const mid = Math.floor(left + (right - left) / 2);
    if (nums[mid] < target) {
      left = mid + 1;
    } else if (nums[mid] > target) {
      right = mid - 1;
    } else if (nums[mid] === target) {
      right = mid - 1;
    }
  }

  if (left >= nums.length || nums[left] != target) {
    return -1;
  }

  return left;
}

方式一的搜索区间为 [left, right)。方式二将搜索区间改为 [left, right] 左闭右闭。因 right 取值为 nums.length - 1,即 nums 的最后一个值。while 的终止条件为 left == right + 1,即代码中使用的 <=。

此时使用 right = mid - 1 而非 right = mid,因搜索区间改变,[left,right] 两端均闭合。

最后判断边界条件,若 left >= nums.length 说明已超出搜索区间,或 left 的值与 target 不同则说明未找到。

如此便与第一种二分搜索算法统一,均为两端闭合的搜索区间,且最后返回 left 变量的值。不过我个人更倾向于这种方式。

寻找最右侧满足条件的值

方式一

function rightBound(nums, target) {
  let left = 0;
  let right = nums.length;
  while (left < right) {
    const mid = Math.floor(left + (right - left) / 2);
    if (nums[mid] === target) {
      left = mid + 1;
    } else if (nums[mid] < target) {
      left = mid + 1;
    } else if (nums[mid] > target) {
      right = mid;
    }
  }
  if (left === 0) return -1;
  return nums[left + 1] === target ? left - 1 : -1;
}

此方式与寻找左侧边界类似,仍使用搜索区间为 [left, right) 左闭右开的方式。关键点在于当 nums[mid]=== target 时,设置 left=mid+1。这样可将搜索区间变为 [mid+1, right)。利用此方式不断增大左边界 left 的值,使区间不断向右靠拢,最终到达右边界。

但此方式最后返回的是 left - 1。因 while 的终止条件为 left === right,此时循环已退出。若已找到,left 则比要锁定的目标索引多 1。因为下面这段代码

if(nums[mid] === target) {
	left = mid + 1
}

所以最后的目标值需 left - 1。

方式二

function rightBound(nums, target) {
  let left = 0;
  let right = nums.length - 1;
  while (left <= right) {
    const mid = Math.floor(left + (right - left) / 2);
    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;
}

此处与左侧边界类似,搜索区间为 [left, right] 左闭右闭。

二分查找大致即这三种情况,也可理解为一种情况不断延伸。记住套路后,便开始刷题巩固吧!

以上就是 JavaScript 中二分查找的例题详解的详细内容,更多关于 JavaScript 二分查找的资料请关注本站其它相关文章!

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章