二分查找(Binary Search)是对一种针对有序数据集合的查找算法,依赖数组,适合静态数据。通过 n/2^k=1(k 是比较次数),可以求得 k=log2^n,因此时间复杂度为高效地 O(logn)。
  其思路很简单,就是每次与区间的中间数据做比较,缩小查找范围,但是期间涉及到的细节很容易踩坑,例如比较时是否带等号、mid值是否要加一等。例题:[704\. 二分查找](https://leetcode-cn.com/problems/binary-search/)。
  LeetCode的[69\. x 的平方根](https://leetcode-cn.com/problems/sqrtx/),x=sqrt(y); y=x^2,由于y和x是单调递增的,因此可用二分查找,每次取中值,不断逼近。另一种解题思路是牛顿迭代法。
  在《剑指offer》一书中曾提到,写出高质量的代码需要了解3个方面:
  (1)规范性,书写清晰、布局清晰和命名合理。
  (2)完整性,完成基本功能、考虑边界条件、做好错误处理。
  (3)鲁棒性,采取防御性编程、处理无效的输入。
  下面是二分查找最基本的代码实现,改编自《[二分搜索](https://labuladong.gitbook.io/algo/di-ling-zhang-bi-du-xi-lie/er-fen-cha-zhao-xiang-jie)》,为了防止mid的溢出,才设计成了 low + Math.floor((high - low) / 2)。
~~~
function binarySearch(nums, target) {
let low = 0, high = ...;
while(...) {
let mid = low + ((high - low) >> 1);
if (nums[mid] == target) {
...
} else if (nums[mid] < target) {
low = ...
} else if (nums[mid] > target) {
high = ...
}
}
return ...;
}
~~~
  其中占位符“...”处就是容易出错的细节部分:
  (1)循环退出条件。
  (2)low 和 high 的更新。
  (3)找到目标值时的处理。
  (4)函数的返回值。
## 一、无重复数据
  在下面的[binarySearch()](https://codepen.io/strick/pen/mdVvWZx)函数中,nums是一个无重复数据的数组,需要注意:
  (1)while循环的判断条件是小于等于,因为high的取值是末尾索引,相当于两端闭区间 \[low, high\]。
  (2)当不匹配时,缩小查找区间,分成两块闭区间:\[mid+1, high\] 或 \[low, mid-1\]。
~~~
function binarySearch(nums, target) {
let low = 0,
high = nums.length - 1; //注意
while(low <= high) { //注意
let mid = low + ((high - low) >> 1);
if (nums[mid] == target) {
return mid; //注意
} else if (nums[mid] < target) {
low = mid + 1; //注意
} else if (nums[mid] > target) {
high = mid - 1; //注意
}
}
return -1; //注意
}
~~~
  面试题11[旋转数组的最小数字](https://leetcode-cn.com/problems/xuan-zhuan-shu-zu-de-zui-xiao-shu-zi-lcof/)。二分查找的思路,用两个指针指向数组的第一个和最后一个元素,如果中间元素位于前面的递增子数组,那么它应该大于或等于第一个指针指向的元素。
  面试题53[在排序数组中查找数字](https://leetcode-cn.com/problems/zai-pai-xu-shu-zu-zhong-cha-zhao-shu-zi-lcof/)。用二分查找锁定指定值,然后在左右两边顺序扫描,直至找出第一个和最后一个指定值。延伸题:[0~n-1 中缺失的数字](https://leetcode-cn.com/problems/que-shi-de-shu-zi-lcof/)。
## 二、含重复数据
  当查找的数组中包含重复数据时,二分查找需要做些处理。
  面试题3[不修改数组找出重复数字](https://leetcode-cn.com/problems/shu-zu-zhong-zhong-fu-de-shu-zi-lcof/)。二分查找思想,从中间数字m分成两部分,1~m和m+1~n,如果1~m的数字超过m,那么区间有重复数字。
**1)查找第一个等于给定值的元素**
  在下面的[binarySearchLow()](https://codepen.io/strick/pen/WNrPjdN)函数中,需要注意:
  (1)while循环的判断条件是小于,因为 low 和 high 相等会陷入死循环。
  (2)查找到匹配的数据不是立即返回,而是缩小范围,并且增加一次 high 的边界值判断。
  (3)在跳出循环后,需要最终判断是否与目标值匹配。
~~~
function binarySearchLow(nums, target) {
let low = 0,
high = nums.length - 1;
while(low < high) { //注意
let mid = low + ((high - low) >> 1);
if (nums[mid] == target) {
high = mid - 1; //注意
if (nums[high] != target) //注意
return mid; //注意
} else if (nums[mid] < target) {
low = mid + 1;
} else if (nums[mid] > target) {
high = mid - 1;
}
}
return nums[low] == target ? low : -1; //注意
}
~~~
  另一个类似的问题是查找最后一个等于给定值的元素,修改匹配时的范围,如下所示。
~~~
function binarySearchHigh(nums, target) {
let low = 0,
high = nums.length - 1;
while(low < high) {
let mid = low + ((high - low) >> 1);
if (nums[mid] == target) {
low = mid + 1; //注意
if (nums[low] != target) //注意
return mid; //注意
} else if (nums[mid] < target) {
low = mid + 1;
} else if (nums[mid] > target) {
high = mid - 1;
}
}
return nums[low] == target ? low : -1;
}
~~~
**2)查找第一个大于等于给定值的元素**
  在下面的[binarySearchLow()](https://codepen.io/strick/pen/NWxogNr)函数中,需要注意:
  (1)修改匹配给定值的条件,变为大于等于。
  (2)额外匹配一次 high 的边界值,成功时直接返回 mid。
  (3)修改跳出循环后的匹配条件。
~~~
function binarySearchLow(nums, target) {
let low = 0,
high = nums.length - 1;
while(low < high) {
let mid = low + ((high - low) >> 1);
if (nums[mid] >= target) { //注意
high = mid - 1; //注意
if (nums[high] < target) //注意
return mid; //注意
} else if (nums[mid] < target) {
low = mid + 1;
}
}
return nums[low] >= target ? low : -1; //注意
}
~~~
  另一个类似的问题是查找最后一个小于等于给定值的元素,修改匹配时的范围,如下所示。
~~~
function binarySearchHigh(nums, target) {
let low = 0,
high = nums.length - 1;
while(low < high) {
let mid = low + ((high - low) >> 1);
if (nums[mid] <= target) { //注意
low = mid + 1; //注意
if (nums[low] > target) //注意
return mid; //注意
} else if (nums[mid] > target) {
high = mid - 1;
}
}
return nums[low] <= target ? low : -1; //注意
}
~~~
  本文的这些示例都没有经过大量测试用例的严谨验证,欢迎指正其中的潜在错误。
*****
> 原文出处:
[博客园-数据结构和算法躬行记](https://www.cnblogs.com/strick/category/1809992.html)
已建立一个微信前端交流群,如要进群,请先加微信号freedom20180706或扫描下面的二维码,请求中需注明“看云加群”,在通过请求后就会把你拉进来。还搜集整理了一套[面试资料](https://github.com/pwstrick/daily),欢迎阅读。
![](https://box.kancloud.cn/2e1f8ecf9512ecdd2fcaae8250e7d48a_430x430.jpg =200x200)
推荐一款前端监控脚本:[shin-monitor](https://github.com/pwstrick/shin-monitor),不仅能监控前端的错误、通信、打印等行为,还能计算各类性能参数,包括 FMP、LCP、FP 等。
- ES6
- 1、let和const
- 2、扩展运算符和剩余参数
- 3、解构
- 4、模板字面量
- 5、对象字面量的扩展
- 6、Symbol
- 7、代码模块化
- 8、数字
- 9、字符串
- 10、正则表达式
- 11、对象
- 12、数组
- 13、类型化数组
- 14、函数
- 15、箭头函数和尾调用优化
- 16、Set
- 17、Map
- 18、迭代器
- 19、生成器
- 20、类
- 21、类的继承
- 22、Promise
- 23、Promise的静态方法和应用
- 24、代理和反射
- HTML
- 1、SVG
- 2、WebRTC基础实践
- 3、WebRTC视频通话
- 4、Web音视频基础
- CSS进阶
- 1、CSS基础拾遗
- 2、伪类和伪元素
- 3、CSS属性拾遗
- 4、浮动形状
- 5、渐变
- 6、滤镜
- 7、合成
- 8、裁剪和遮罩
- 9、网格布局
- 10、CSS方法论
- 11、管理后台响应式改造
- React
- 1、函数式编程
- 2、JSX
- 3、组件
- 4、生命周期
- 5、React和DOM
- 6、事件
- 7、表单
- 8、样式
- 9、组件通信
- 10、高阶组件
- 11、Redux基础
- 12、Redux中间件
- 13、React Router
- 14、测试框架
- 15、React Hooks
- 16、React源码分析
- 利器
- 1、npm
- 2、Babel
- 3、webpack基础
- 4、webpack进阶
- 5、Git
- 6、Fiddler
- 7、自制脚手架
- 8、VSCode插件研发
- 9、WebView中的页面调试方法
- Vue.js
- 1、数据绑定
- 2、指令
- 3、样式和表单
- 4、组件
- 5、组件通信
- 6、内容分发
- 7、渲染函数和JSX
- 8、Vue Router
- 9、Vuex
- TypeScript
- 1、数据类型
- 2、接口
- 3、类
- 4、泛型
- 5、类型兼容性
- 6、高级类型
- 7、命名空间
- 8、装饰器
- Node.js
- 1、Buffer、流和EventEmitter
- 2、文件系统和网络
- 3、命令行工具
- 4、自建前端监控系统
- 5、定时任务的调试
- 6、自制短链系统
- 7、定时任务的进化史
- 8、通用接口
- 9、微前端实践
- 10、接口日志查询
- 11、E2E测试
- 12、BFF
- 13、MySQL归档
- 14、压力测试
- 15、活动规则引擎
- 16、活动配置化
- 17、UmiJS版本升级
- 18、半吊子的可视化搭建系统
- 19、KOA源码分析(上)
- 20、KOA源码分析(下)
- 21、花10分钟入门Node.js
- 22、Node环境升级日志
- 23、Worker threads
- 24、低代码
- 25、Web自动化测试
- 26、接口拦截和页面回放实验
- 27、接口管理
- 28、Cypress自动化测试实践
- 29、基于Electron的开播助手
- Node.js精进
- 1、模块化
- 2、异步编程
- 3、流
- 4、事件触发器
- 5、HTTP
- 6、文件
- 7、日志
- 8、错误处理
- 9、性能监控(上)
- 10、性能监控(下)
- 11、Socket.IO
- 12、ElasticSearch
- 监控系统
- 1、SDK
- 2、存储和分析
- 3、性能监控
- 4、内存泄漏
- 5、小程序
- 6、较长的白屏时间
- 7、页面奔溃
- 8、shin-monitor源码分析
- 前端性能精进
- 1、优化方法论之测量
- 2、优化方法论之分析
- 3、浏览器之图像
- 4、浏览器之呈现
- 5、浏览器之JavaScript
- 6、网络
- 7、构建
- 前端体验优化
- 1、概述
- 2、基建
- 3、后端
- 4、数据
- 5、后台
- Web优化
- 1、CSS优化
- 2、JavaScript优化
- 3、图像和网络
- 4、用户体验和工具
- 5、网站优化
- 6、优化闭环实践
- 数据结构与算法
- 1、链表
- 2、栈、队列、散列表和位运算
- 3、二叉树
- 4、二分查找
- 5、回溯算法
- 6、贪心算法
- 7、分治算法
- 8、动态规划
- 程序员之路
- 大学
- 2011年
- 2012年
- 2013年
- 2014年
- 项目反思
- 前端基础学习分享
- 2015年
- 再一次项目反思
- 然并卵
- PC网站CSS分享
- 2016年
- 制造自己的榫卯
- PrimusUI
- 2017年
- 工匠精神
- 2018年
- 2019年
- 前端学习之路分享
- 2020年
- 2021年
- 2022年
- 2023年
- 日志
- 2020