
图:闭区间二分循环结束时的左右指针位置(查找第一个 )
刷题建议:初次刷题可以只刷难度低于 分的题目。难度更高的题目常常结合其他算法(数据结构、图论等)。
一、二分查找
设 为递增(非递减)数组,长为 。
二分查找常用转化表:
| 需求 | 写法 | 如果不存在 |
|---|---|---|
| 的第一个整数的下标 | 结果为 | |
| 的第一个整数的下标 | 结果为 | |
| 的最后一个整数的下标 | 结果为 | |
| 的最后一个整数的下标 | 结果为 |
| 需求 | 写法 |
|---|---|
| 的整数个数 | |
| 的整数个数 | |
| 的整数个数 | |
| 的整数个数 |
注意 和 互为补集,元素个数之和为 。 和 同理。
§1.1 基础
- 34. 在排序数组中查找元素的第一个和最后一个位置 推荐阅读我题解中的答疑
- 35. 搜索插入位置 推荐阅读我题解中的答疑
- 704. 二分查找
- 744. 寻找比目标字母大的最小字母
- 2529. 正整数和负整数的最大计数 做到
§1.2 进阶
部分题目需要先排序,然后在有序数组上二分查找。
- 1385. 两个数组间的距离值 1235
- 1170. 比较字符串最小字母出现频次 1432
- 2300. 咒语和药水的成功对数 1477
- 2389. 和有限的最长子序列 非暴力做法
- 2080. 区间内查询数字的频率 1702
- 3488. 距离最小相等元素查询 做法不止一种
- 2563. 统计公平数对的数目 1721
- 1146. 快照数组 1771
- 981. 基于时间的键值存储 同 1146 题
- 3508. 设计路由器 1851
- 658. 找到 K 个最接近的元素
- 1818. 绝对差值和 1934
- 911. 在线选举 2001
- LCP 08. 剧情触发时间
- 1182. 与目标颜色间的最短距离(会员题)
- 3893. 最大重叠区间团队规模(会员题)
- 2819. 购买巧克力后的最小相对损失(会员题)
思维扩展:
- 74. 搜索二维矩阵
- 1287. 有序数组中出现次数超过 25% 的元素 做到
- 2476. 二叉搜索树最近节点查询 1597
- 2070. 每一个查询的最大美丽值 1724
- 4012. 统计每个班次结束后的未完成任务数 1736
- 1150. 检查一个数是否在数组中占绝大多数(会员题)
二、二分答案
“花费一个 的时间,增加了一个条件。” —— 二分答案
§2.1 求最小
题目求什么,就二分什么。
答疑
问:如何把二分答案与数组上的二分查找联系起来?
答:假设答案在区间 中,我们相当于在一个虚拟数组 中二分找第一个(或者最后一个)值为 的 。这同样可以用红蓝染色法思考。
问:有些题目,明明 可以是答案,但却不在初始二分区间中。比如闭区间二分初始化 (或者开区间 ),这不会算错吗?
答:不会算错。注意「答案所在区间」和「二分区间」是两个概念。想一想,如果二分的 循环每次更新的都是 ,那么最终答案是什么?正好就是 。一般地,如果一开始就能确定 一定可以满足题目要求,那么 是不需要在二分区间中的。换句话说,二分区间是「尚未确定是否满足题目要求」的数的范围。那些在区间外面的数,都是已确定的满足(不满足)题目要求的数。
问:什么是循环不变量?
答:想一想,对于求最小的题目,开区间二分的写法,为什么最终返回的是 ,而不是别的数?在初始化(循环之前)、循环中、循环结束后,都时时刻刻保证 check(right) == true 和 check(left) == false,这就叫循环不变量。根据循环不变量,循环结束时 ,那么 就是最小的满足要求的数(因为再 就不满足要求了),所以答案是 。
注:部分题目可以优化二分边界,减少二分次数,从而减少代码运行时间。对于初次接触二分答案的同学,无需强求自己写出最优的代码,设定一个比较大的二分上界也是可以的。
开区间二分模板(求最小):
class Solution:
# 计算满足 check(x) == True 的最小整数 x
def binarySearchMin(self, nums: List[int]) -> int:
# 二分猜答案:判断 mid 是否满足题目要求
def check(mid: int) -> bool:
# TODO
left = # 循环不变量:check(left) 恒为 False
right = # 循环不变量:check(right) 恒为 True
while left + 1 < right: # 开区间不为空
mid = (left + right) // 2
if check(mid): # 说明 check(>= mid 的数) 均为 True
right = mid # 接下来在 (left, mid) 中二分答案
else: # 说明 check(<= mid 的数) 均为 False
left = mid # 接下来在 (mid, right) 中二分答案
# 循环结束后 left+1 = right
# 此时 check(left) == False 而 check(left+1) == check(right) == True
# 所以 right 就是最小的满足 check 的值
return right- 1283. 使结果不超过阈值的最小除数 1542
- 2187. 完成旅途的最少时间 1641
- 1011. 在 D 天内送达包裹的能力 1725
- 875. 爱吃香蕉的珂珂 1766
- 4008. 击败所有怪物的最小初始强度 1776
- 3296. 移山所需的最少秒数 注:由于有其他做法,难度分会低一些,二分做法估计 1850
- 3639. 变为活跃状态的最小时间 1853
- 475. 供暖器
- 2594. 修车的最少时间 1915
- 1482. 制作 m 束花所需的最少天数 1946
- 3924. 有限重边的最小阈值路径 2080
- 3048. 标记所有下标的最早秒数 I 2263
- 3807. 修复边以遍历图的最小成本(会员题)
- 2604. 吃掉所有谷子的最短时间(会员题)
- 2702. 使数字变为非正数的最小操作次数(会员题)
思维扩展:
- 3824. 减小数组使其满足条件的最小 K 值 1531
- 1870. 准时到达的列车最小时速 1676 避免浮点数
- 3453. 分割正方形 I 1735 浮点二分(也可以整数二分)
§2.2 求最大
请注意「求最小」和「求最大」在二分写法上的区别。
「求最小」和二分查找求「排序数组中某元素的第一个位置」是类似的,按照红蓝染色法,左边是不满足要求的(红色),右边则是满足要求的(蓝色)。
「求最大」的题目则相反,左边是满足要求的(蓝色),右边是不满足要求的(红色)。这会导致二分写法和上面的「求最小」有一些区别。
以开区间二分为例:
- 求最小:
check(mid) == true时更新right = mid,反之更新left = mid,最后返回right。 - 求最大:
check(mid) == true时更新left = mid,反之更新right = mid,最后返回left。
对于开区间写法,简单来说 check(mid) == true 时更新的是谁,最后就返回谁。相比其他二分写法,开区间写法不需要思考加一减一等细节,推荐使用开区间写二分。
开区间二分模板(求最大):
class Solution:
# 计算满足 check(x) == True 的最大整数 x
def binarySearchMax(self, nums: List[int]) -> int:
# 二分猜答案:判断 mid 是否满足题目要求
def check(mid: int) -> bool:
# TODO
left = # 循环不变量:check(left) 恒为 True
right = # 循环不变量:check(right) 恒为 False
while left + 1 < right:
mid = (left + right) // 2
if check(mid):
left = mid # 注意这里更新的是 left,和上面的模板反过来
else:
right = mid
# 循环结束后 left+1 = right
# 此时 check(left) == True 而 check(left+1) == check(right) == False
# 所以 left 就是最大的满足 check 的值
return left # check 更新的是谁,最终就返回谁- 275. H 指数 II
- 2226. 每个小孩最多能分到多少糖果 1646
- 2982. 找出出现至少三次的最长特殊子字符串 II 1773
- 2576. 求出最多标记下标 1843
- 1898. 可移除字符的最大数目 1913
- 1802. 有界数组中指定下标处的最大值 1929
- 1642. 可以到达的最远建筑 1962
- 2861. 最大合金数 1981
- 3007. 价值和小于等于 K 的最大数字 2258
- 2141. 同时运行 N 台电脑的最长时间 2265
- 2258. 逃离火灾 2347
- 2071. 你可以安排的最多任务数目 2648
- LCP 78. 城墙防线
- 1618. 找出适应屏幕的最大字号(会员题)
- 1891. 割绳子(会员题)
- 2137. 通过倒水操作让所有的水桶所含水量相等(会员题)
- 3344. 最大尺寸数组(会员题)
- 644. 子数组最大平均数 II(会员题)
§2.3 二分间接值
二分的不是答案,而是一个和答案有关的值(间接值)。
- 3143. 正方形中的最多点数 1697
- 1648. 销售价值减少的颜色球 2050
§2.4 最小化最大值
本质是二分答案求最小。二分的 表示上界。
好比用一个盖子(上界)去压住最大值,看看能否压住( 函数)。
- 410. 分割数组的最大值
- 2064. 分配给商店的最多商品的最小值 1886
- 3613. 最小化连通分量的最大成本 约 1900
- 1760. 袋子里最少数目的球 1940
- 1631. 最小体力消耗路径 1948
- 2439. 最小化数组中的最大值 1965
- 2560. 打家劫舍 IV 2081
- 778. 水位上升的泳池中游泳 2097 相当于最小化路径最大值
- 2616. 最小化数对的最大差值 2155
- 3419. 图的最大边权的最小值 2243
- 2513. 最小化两个数组中的最大值 2302
- 3733. 完成所有送货任务的最少时间 同 2513 题
- 3399. 字符相同的最短子字符串 II 2376
- 3605. 数组的最小稳定性因子 2410
- LCP 12. 小张刷题计划
- 774. 最小化去加油站的最大距离(会员题)
§2.5 最大化最小值
本质是二分答案求最大。二分的 表示下界。
- 3281. 范围内整数的最大得分 1768
- 3620. 恢复网络路径 1998
- 2517. 礼盒的最大甜蜜度 2021
- 1552. 两球之间的磁力 同 2517 题
- 3710. 最大划分因子 2135
- 2812. 找出最安全路径 2154
- 2528. 最大化城市的最小电量 2236
- 3600. 升级后最大生成树稳定性 2301 做法不止一种
- 3449. 最大化游戏分数的最小值 2748
- 3464. 正方形上的点之间的最大距离 2806
- 1102. 得分最高的路径(会员题)
- 1231. 分享巧克力(会员题)
§2.6 第 K 小/大
例如数组 ,其中第 小、第 小和第 小的数都是 ,第 小和第 小的数都是 。
对于第 小/大问题,有如下转化套路:
- 第 小等价于:求最小的 ,满足 的数至少有 个。
- 第 大等价于:求最大的 ,满足 的数至少有 个。
注 1:一般规定 从 开始,而不是像数组下标那样从 开始。
注 2:部分题目也可以用堆解决。
- 668. 乘法表中第 K 小的数
- 378. 有序矩阵中第 K 小的元素
- 719. 找出第 K 小的数对距离
- 878. 第 N 个神奇数字 1897
- 1201. 丑数 III 2039
- 793. 阶乘函数后 K 个零 2100
- 373. 查找和最小的 K 对数字
- 1439. 有序矩阵中的第 k 个最小数组和 2134
- 786. 第 K 个最小的质数分数 2169
- 3971. 最大总价值 2195
- 3116. 单面值组合的第 K 小金额 2387
- 3134. 找出唯一性数组的中位数 2451
- 2040. 两个有序数组的第 K 小乘积 2518
- 2386. 找出数组的第 K 大和 2648 转化
- 1508. 子数组和排序后的区间和
- 3691. 最大子数组总值 II 做到时间复杂度与 无关
- 3520. 逆序对计数的最小阈值(会员题)
- 1918. 第 K 小的子数组和(会员题)
三、三分法
- 1515. 服务中心的最佳位置 2157 做法不止一种
四、其他
- 69. x 的平方根 二分求最大的 ,满足 (也可以二分求最小的满足 的 ,减一得到答案)
- 278. 第一个错误的版本
- 374. 猜数字大小
- 162. 寻找峰值
- 1901. 寻找峰值 II
- 852. 山脉数组的峰顶索引
- 1095. 山脉数组中查找目标值 1827
- 153. 寻找旋转排序数组中的最小值
- 154. 寻找旋转排序数组中的最小值 II
- 33. 搜索旋转排序数组
- 81. 搜索旋转排序数组 II
- 222. 完全二叉树的节点个数
- 1539. 第 k 个缺失的正整数
- 3911. 移除子数组元素后第 K 小偶数 2156
- 540. 有序数组中的单一元素
- 4. 寻找两个正序数组的中位数
- 1064. 不动点(会员题)
- 702. 搜索长度未知的有序数组(会员题)
- 2936. 包含相等值数字块的数量(会员题)
- 1060. 有序数组中的缺失元素(会员题)
- 1198. 找出所有行中最小公共元素(会员题)
- 1428. 至少有一个 1 的最左端列(会员题)
- 1533. 找到最大整数的索引(会员题)
- 2387. 行排序矩阵的中位数(会员题)
- 302. 包含全部黑色像素的最小矩形(会员题)
关联题单
二分答案的一个难点是 check 函数怎么写,这会涉及到贪心等技巧,可以练练下面的贪心题单(主要是第一章节)。
算法题单
- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
- 单调栈(基础/矩形面积/贡献法/最小字典序)
- 网格图(DFS/BFS/综合应用)
- 位运算(基础/性质/拆位/试填/恒等式/思维)
- 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
- 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
- 链表、树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA)
- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
欢迎关注 B站@灵茶山艾府
如果你发现有题目可以补充进来,欢迎评论反馈。


