V

算法可视化

ZHEN
分享到微博分享到 XGitHub 仓库个人主页
学习工具算法复杂度速查算法学习路径
数据结构数组链表栈队列树堆哈希表图字典树并查集LRU 缓存跳表线段树B+ 树布隆过滤器树状数组
经典排序算法冒泡排序鸡尾酒排序双调排序选择排序插入排序二分插入排序希尔排序归并排序自顶向下归并快速排序三路快排双轴快排堆排序计数排序基数排序桶排序
图算法Dijkstra 最短路Kruskal 最小生成树Prim 最小生成树Bellman-Ford 最短路拓扑排序Floyd 多源最短路强连通分量2-SAT最大流二分图匹配LCA 倍增欧拉路径
动态规划编辑距离0-1 背包完全背包最长公共子序列最长递增子序列硬币找零方案数石子合并旅行商 TSP树形 DP数位 DP换根 DP
回溯与搜索N 皇后子集生成全排列组合总和迷宫寻路岛屿数量单词搜索数独A* 寻路
字符串KMP 字符串匹配Rabin-KarpBoyer-MooreManacher后缀数组LCP / height 数组AC 自动机Z 函数
数学与数论埃氏筛线性筛欧几里得算法快速幂扩展欧几里得中国剩余定理欧拉函数米勒-拉宾FFTPollard's Rho
计算几何凸包旋转卡壳最近点对线段相交扫描线求交
查找二分查找二分边界旋转数组搜索二分答案三分查找

二分查找

查找 · 有序数组 · 每一步扔掉一半

猜数字的最优策略

「我想了个 1 到 1000 的数,你每猜一次我告诉你大了还是小了」——最优策略人人都会:猜中间。答案「大了」就扔掉下半、「小了」就扔掉上半,每次候选减半,10 次以内必中。二分查找就是这个策略在有序数组上的算法化:维护候选区间 [lo, hi],每步和中点 arr[mid] 比较,扔掉不可能的一半——O(log n),10 亿元素最多 30 次比较。

看得见的「扔掉一半」

下面两次查找:先找 17(命中),再找 4(不存在)。红/蓝/黄箭头是 lo / mid / hi,亮着的柱子 = 当前候选区间,暗下去的都是已被证明不可能的。注意第二次:区间收缩到 [2, 1](lo > hi)彻底清空——循环退出、返回 -1。右侧代码随每一步同步高亮。

1
3
5
7
9
11
13
15
17
19

前提:数组有序。找 target = 17:候选区间 [lo, hi] = [0, 9](高亮部分),每次和中点比、扔掉不可能的一半

1function binarySearch(a: number[], t: number): number {
2 let lo = 0, hi = a.length - 1; // 候选区间 [lo, hi]
3 while (lo <= hi) {
4 const mid = (lo + hi) >> 1; // 探针:取中点
5 if (a[mid] === t) return mid; // 命中
6 if (a[mid] < t) lo = mid + 1; // 目标在右半:扔掉左半
7 else hi = mid - 1; // 目标在左半:扔掉右半
8 }
9 return -1; // 区间清空:不存在
10}
n10
target17
[lo, hi][0, 9]
mid—
1 / 16

不变量与小坑

前提:有序。乱序数组要先排序(O(n log n)),一次查找多、排序摊销就值。
不变量:目标若存在,必在 [lo, hi] 内——每次收缩都保持这句话为真,正确性就有了。
溢出坑:(lo + hi) / 2 在大数组下可能溢出,稳妥写 lo + ((hi - lo) >> 1)。
复杂度:每步区间减半 → ⌈log₂(n+1)⌉ 次比较封顶。

基础版只回答「在不在」。真正让二分成为面试与竞赛常客的是它的变体:重复元素里找左右边界(lower/upper bound)、旋转有序数组里定位、以及跳出数组、在答案空间上二分(「最小可行速度」「最大最小值」)——这些是查找大类接下来几页的故事。