LeetCode Hot 100 题解 · 二分篇
- 本专题收录 LeetCode Hot 100 中所有 二分查找 相关题目。
- 每道题提供最直接、最容易理解的解题思路,包含详细注释的代码实现,方便笔试和面试复习。
前置:二分的写法和形式,先想清楚"找什么"
二分不是"排好序才能用",本质是在一个单调的判定上砍半 :能回答"答案在 mid 的哪一边",就能每次丢掉一半。写二分翻车基本都翻在两件事上------没想清楚到底在找什么、区间形式和收缩方式不配套。先定目标,再定形式,剩下的全是推出来的,不用背。
1. 三种区间形式
| 形式 | 初始化 | 循环条件 | 收缩方式 | 适合找什么 |
|---|---|---|---|---|
| 闭区间 | l = 0, r = n - 1 |
l <= r |
命中即 return;找边界配 l = mid + 1 / r = mid - 1 |
一个具体的数(可能不存在) |
| 左闭右开 | l = 0, r = n |
l < r |
r = mid / l = mid + 1,出循环 l 即答案 |
第一个满足条件的位置 |
| 红蓝开区间(本篇统一用) | l = -1, r = n |
l + 1 < r |
r = mid / l = mid,不用 ±1 |
左右边界都行,心智负担最小 |
2. 红蓝开区间写法(l = -1, r = n)
把二分看成染色:数组每个位置要么"满足条件"(蓝),要么"不满足"(红)。维护两个边界:
java
int l = -1, r = n; // 不变量:[0, l] 全是"否"(红),[r, n-1] 全是"是"(蓝)
while (l + 1 < r) { // 中间还有没染色的格子
int mid = l + (r - l) / 2;
if (check(mid)) {
r = mid; // mid 染蓝:第一个"是"最靠前也就是 mid
} else {
l = mid; // mid 染红:最后一个"否"最靠后也就是 mid
}
}
// 出循环 l + 1 == r:l = 最后一个"否",r = 第一个"是"
三个性质,决定了它为什么好用:
- 初始化天然成立 :
l = -1、r = n是两个"虚拟"哨兵位,左侧红的集合和右侧蓝的集合都是空------不用验证nums[0]、nums[n-1]满不满足条件。 - 绝不死循环 :循环内
l + 1 < r保证mid严格落在(l, r)开区间里,l = mid严格变大、r = mid严格变小,区间每轮必缩。闭区间写法的l = mid死循环问题(mid == l时不动)在这里被结构性消灭。 - 一个模板两种答案 :要"第一个满足 "的位置取
r;要"最后一个满足 "的位置取l------34.在排序数组中查找元素的第一个和最后一个位置 正好两头都要。
要查"具体值存不存在",加一个 == 分支提前 return(35.搜索插入位置 的写法);红蓝式还能直接用在旋转数组 这种"整体无序但方向可判"的场上(33.搜索旋转排序数组)。
3. 三个通用提醒
mid = l + (r - l) / 2防l + r溢出------数学上和(l + r) / 2等价,但前者不会先溢出。- 收缩用
l = mid(不带 +1)时,mid必须能严格大于 l ,否则死循环。红蓝开区间的下取整天然安全;但区间收成while (left < right)且left = i时就要上取整 (i = left + (right - left + 1) / 2)------4.寻找两个正序数组的中位数 的写法,坑和救法都在这。 - 判定条件"单调"比"数组有序"宽得多:旋转数组"target 在 mid 的哪一边"可判、4.寻找两个正序数组的中位数 里"切点左边元素个数够不够"也是单调的。
📑 目录
| 题号 | 题目 | 难度 | 核心思路 |
|---|---|---|---|
| 35 | 35.搜索插入位置 | 🟢简单 | 红蓝开区间的入门:三分支精确查找 ------== 提前 return,< 染红 l = mid、> 染蓝 r = mid。不变量是 [0, l] 全 < target、[r, n) 全 > target:存在时中途命中;不存在时 l 和 r 把 target 夹 在中间,r 正是插入位置。 |
| 74 | 74.搜索二维矩阵 | 🟠中等 | 从右上角出发的 Z 字形查找 :当前值比 target 大就左移(排除一整列,下面只会更大),比 target 小就下移(排除一整行,左边只会更小),每步排掉一行或一列, O ( m + n ) O(m+n) O(m+n)。它只要求"行有序 + 列有序",好写好记。要满足进阶 O ( log ( m × n ) ) O(\log (m \times n)) O(log(m×n)) 得认出矩阵其实是一维有序数组蜷起来的 ,拉平下标一次二分(mid / n 定行、mid % n 定列)。 |
| 34 | 34.在排序数组中查找元素的第一个和最后一个位置 | 🟠中等 | 精确查找碰到哪个下标全凭运气,要边界 就得换目标:首位置 = 第一个 ≥ target (取 r),尾位置 = 最后一个 ≤ target (取 l)------两个对称目标,同一个红蓝模板改一个比较符跑两遍,最后各验一次值兜底。 |
| 33 | 33.搜索旋转排序数组 | 🟠中等 | 旋转数组左大右小两段 ,拿最右元素 back(第二段最大值)当参照:> back 是第一段、≤ back 是第二段。每次看 target 和 nums[mid] 谁在左边------同段直接比值 (段内升序),异段看段位(第一段整体在第二段左边),六种组合就一个判定,红蓝收缩到收敛点再验证。 |
| 153 | 153.寻找旋转排序数组中的最小值 | 🟠中等 | 最小值 = 第二段的头 。判定只看 nums[mid] 和最右元素:> 说明 mid 还在第一段(断崖在右边)染红,≤ 说明 mid 已在第二段(自己可能就是最小)染蓝,r 收敛到第二段第一个元素。和 l 比不行------nums[mid] > nums[l] 区分不出"没旋转"和"mid 在第一段",方向不唯一;和最右比每种情况方向唯一。 |
| 4 | 4.寻找两个正序数组的中位数 | 🔴困难 | 中位数 = 合并后左右两半等长的切点 。在短数组上二分切点 i(j 由"左半凑够 totalLeft 个"唯一解出):i 越大 nums1[i-1] 越大、nums2[j] 越小,所以 nums1[i-1] > nums2[j] 对 i 单调------只查这一个不等式 ,二分找最后一个满足它的 i(短数组保证另一个方向自动成立),上取整防死循环,正负无穷当空侧哨兵。 O ( log min ( m , n ) ) O(\log \min(m,n)) O(logmin(m,n))。 |
35.搜索插入位置
题目链接 :35.搜索插入位置
题目描述 :给一个升序 排列的数组 nums(无重复元素)和目标值 target,如果存在就返回下标;不存在返回它按顺序插入后的位置 。要求 O ( log n ) O(\log n) O(logn)。
思路一:红蓝开区间三分支(== 提前命中,l 和 r 把 target 夹出来)
核心思想:
- 红蓝开区间最直接的用法,三个分支各管一件事:
==提前 return (存在的话根本不用等收敛);< target染红l = mid;> target染蓝r = mid。 - 不变量是:
[0, l]全部< target,[r, n)全部> target。target 存在时会被中途命中 ;不存在时,红蓝边界收敛到它"该在的位置"两侧------r就是第一个> target的位置,正是插入位置(后面所有人都得给它让位)。 - 所有元素都比 target 小时
r收敛到n------正好是插到末尾的位置,不用特判。一个模板同时覆盖"存在"和"不存在",不用分两种情况讨论。
时间复杂度 : O ( log n ) O(\log n) O(logn),其中 n 为数组长度。
空间复杂度 : O ( 1 ) O(1) O(1)。
java
class Solution {
public int searchInsert(int[] nums, int target) {
int n = nums.length, l = -1, r = n;
// 不变量:[0, l] 全 < target,[r, n) 全 > target
while (l + 1 < r) {
int mid = l + (r - l) / 2;
if (nums[mid] > target) {
r = mid; // mid 及其右侧都 > target:插入位在 mid 左边(含 mid)
} else if (nums[mid] < target) {
l = mid; // mid 及其左侧都 < target:插入位在 mid 右边
} else {
return mid; // 命中:target 存在,就是它
}
}
return r; // 收敛点:第一个 > target 的位置 = 插入位置
}
}
这版等价于"找第一个
≥ target的下标":无重复元素下,target 不存在时"第一个>"和"第一个≥"是同一个位置。后者是更通用的抽象(有重复时依然成立),34.在排序数组中查找元素的第一个和最后一个位置 就靠它。
74.搜索二维矩阵
题目链接 :74.搜索二维矩阵
题目描述 :给一个 m × n 矩阵:每行从左到右升序 ,且每行第一个整数大于前一行的最后一个整数 。再给 target,判断它在不在矩阵里。
思路一:Z 字形查找(右上角起步,每步排掉一行或一列)
核心思想:
- "每行首元素大于上一行尾元素"蕴含两件事:每行有序 ,且每列从上到下递增 。站在右上角 看:往左一路变小(本行),往下一路变大(本列)------当前位置是一行里最大的、一列里最小的,一大一小两个方向都可控。
- 于是每步都能排除一片:当前值
< target→ 本行左边全更小,整行作废,下移;当前值> target→ 本列下面全更大,整列作废,左移。每步至少排掉一行或一列,最坏m + n步出结果。 - 起点必须是右上或左下------左上角是"双小"(比 target 小时不知道该往哪走)、右下角是"双大",只有对角的两端才能双向排除。
- 这写法只依赖"行有序 + 列有序" (240.搜索二维矩阵 II 的弱条件也够用),好写好记,首选。代价是 O ( m + n ) O(m + n) O(m+n) 摸不到进阶的 O ( log ( m × n ) ) O(\log (m \times n)) O(log(m×n))------那得上思路二。
时间复杂度 : O ( m + n ) O(m + n) O(m+n),其中 m、n 为矩阵行数、列数,每步下移或左移一格,最多走 m + n - 1 步。
空间复杂度 : O ( 1 ) O(1) O(1)。
java
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
int m = matrix.length, n = matrix[0].length;
int r = 0, c = n - 1; // 从右上角出发:本行最大、本列最小,两个方向都可控
while (r < m && c >= 0) {
int cur = matrix[r][c];
if (cur < target) {
++r; // 本行左边全更小,整行作废,下移
} else if (cur > target) {
--c; // 本列下面全更大,整列作废,左移
} else {
return true;
}
}
return false; // 走出边界还没碰上:不存在
}
}
思路二:拉平成一维做一次二分(满足 O ( log ( m × n ) ) O(\log (m \times n)) O(log(m×n)) 的进阶)
核心思想 :题目的强条件("每行首元素大于上一行尾元素")意味着按行优先读整个矩阵,就是一个严格递增的一维数组 ------矩阵只是它蜷成 m 行的形状。直接当一维数组二分:mid 是一维下标,映射回二维就是 行 = mid / n、列 = mid % n。Z 字形没用上这个强条件(它弱条件就能跑),条件越强、能上的解法越快,这正是两道矩阵搜索题的分界线。
时间复杂度 : O ( log ( m × n ) ) O(\log (m \times n)) O(log(m×n)),等价于对长度 m×n 的一维数组二分。
空间复杂度 : O ( 1 ) O(1) O(1)。
java
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
int m = matrix.length, n = matrix[0].length;
int l = -1, r = m * n; // 红蓝开区间,把矩阵当一维数组 [0, m*n-1]
while (l + 1 < r) {
int mid = l + (r - l) / 2;
int val = matrix[mid / n][mid % n]; // 一维下标映射回二维:商是行,余是列
if (val < target) {
l = mid;
} else {
r = mid; // >= target 染蓝:r 收敛到第一个 >= target 的位置
}
}
return r < m * n && matrix[r / n][r % n] == target; // 收敛点验证
}
}
34.在排序数组中查找元素的第一个和最后一个位置
题目链接 :34.在排序数组中查找元素的第一个和最后一个位置
题目描述 :给一个非递减 (有重复)的升序数组 nums 和 target,返回它在数组里出现的第一个和最后一个下标 (闭区间 [first, last]);不存在返回 [-1, -1]。要求 O ( log n ) O(\log n) O(logn)。
思路一:两次红蓝二分(≥ target 取 r 找首、≤ target 取 l 找尾)
核心思想:
- 数组有重复,精确查找碰到哪个下标全凭运气------要边界就得换目标。首位置 = 第一个
≥ target的下标 :红蓝跑完取r;若这个位置的值恰好是 target,它就是首,值更大说明 target 根本不存在。 - 尾位置 = 最后一个
≤ target的下标 :判定条件改成≤染红的方向,跑完取l;若l处的值恰好是 target,它就是尾。两个目标完全对称 (一个找第一个"是"、一个找最后一个"是"),同一个红蓝模板只改一个比较符,跑两遍。 - 两次结果各验一次值兜底:首位置越界(
r == n,全比 target 小)或值不等,返回[-1, -1];尾位置同理。验证不能省------二分只保证"条件边界",不保证 target 真的存在。
时间复杂度 : O ( log n ) O(\log n) O(logn),其中 n 为数组长度,两次二分。
空间复杂度 : O ( 1 ) O(1) O(1)。
java
class Solution {
public int[] searchRange(int[] nums, int target) {
int n = nums.length;
// 第一趟:找第一个 >= target(取 r)
int l = -1, r = n;
while (l + 1 < r) {
int mid = l + (r - l) / 2;
if (nums[mid] < target) {
l = mid; // 染红:[0, l] 全 < target
} else { // >= 就动右边
r = mid; // 染蓝:[r, n) 全 >= target,r = 第一个 >= target
}
}
int start = r;
if (start >= n || nums[start] != target) {
start = -1; // 越界(全比它小)或落点不是它:不存在
}
// 第二趟:找最后一个 <= target(取 l),比较符号反过来
l = 0; // 升序保证:nums[0] > target 时后面全 > target,r 会收到 0,值验证兜底
r = n;
while (l + 1 < r) {
int mid = l + (r - l) / 2;
if (nums[mid] <= target) {
l = mid; // 染红:[0, l] 全 <= target,l = 最后一个 <= target
} else { // > 就动右边
r = mid;
}
}
int end = l;
if (end >= n || nums[end] != target) {
end = -1;
}
return new int[] { start, end };
}
}
"最后一个
≤ target"比"第一个> target退一格"更直接------不用退格,也不用target + 1这种"查一个不存在的数"的技巧,红蓝模板取l天然就是"最后一个满足"。一套模板、一个符号之差,左边界右边界都有了,这就是红蓝式的第三个好处。
33.搜索旋转排序数组
题目链接 :33.搜索旋转排序数组
题目描述 :一个原本升序、元素互不相同 的数组,在某个未知下标处旋转 了一次(前段挪到后面),比如 [0,1,2,4,5,6,7] 旋转成 [4,5,6,7,0,1,2]。给旋转后的数组和 target,返回 target 的下标,不在返回 -1。要求 O ( log n ) O(\log n) O(logn)。
思路一:红蓝二分判相对位置(同段比值、异段看段位)
核心思想:
- 旋转数组是左大右小两段 (第一段整体大于第二段,第二段最大值就是数组最右元素
back = nums[n-1])。back是天然的段判据:> back是第一段,≤ back是第二段------一个问题"你在哪段"一个比较就答了。 - 二分要判的是:target 在
nums[mid]的左边还是右边 (旋转意义下的位置关系)。两种情况:- 同段 :段内各自升序,直接比值------
cur >= target则 target 在 mid 左边(或就是 mid),否则在右边; - 异段:段位定方向------第一段整体在第二段左边。target 在第一段、cur 在第二段 → target 在 cur 左边;反过来在右边。
- 同段 :段内各自升序,直接比值------
- target 段位 × cur 段位 × 段内大小,展开正好六种组合 ,每种都唯一给出"往左(
r = mid)"或"往右(l = mid)"------本质就是一句"target 在 cur 的哪边"。红蓝收缩到收敛点r,验证nums[r] == target。 - 每轮区间确定地砍半, O ( log n ) O(\log n) O(logn) 保住。写的时候六分支别抄:记住"同段比值、异段看段位",现场推每一支,比背模板稳。
时间复杂度 : O ( log n ) O(\log n) O(logn),其中 n 为数组长度。
空间复杂度 : O ( 1 ) O(1) O(1)。
java
class Solution {
public int search(int[] nums, int target) {
int l = -1, r = nums.length;
// 左大右小:back = 最右元素 = 第二段最大值,是划分两段的参照
int back = nums[r - 1];
while (l + 1 < r) {
int mid = l + (r - l) / 2;
int cur = nums[mid];
// 判定只有一句:target 在 cur 左边 -> r = mid;在右边 -> l = mid
// 同段(都 <= back 或都 > back)比大小,异段看段位(第一段整体在左)
if (target <= back && cur <= back && cur >= target) {
r = mid; // 同在二段,cur >= target:target 在 cur 左或即 cur
} else if (target <= back && cur <= back && cur < target) {
l = mid; // 同在二段,cur < target
} else if (target <= back && cur > back) { // 太大了
l = mid; // target 在二段,cur 在一段:target 在右边
} else if (target > back && cur > back && cur >= target) {
r = mid; // 同在一段,cur >= target
} else if (target > back && cur > back && cur < target) {
l = mid; // 同在一段,cur < target
} else {
r = mid; // target 在一段,cur 在二段:target 在左边
}
}
return r < nums.length && nums[r] == target ? r : -1; // 收敛点验证
}
}
本题元素互不相同 是关键前提------有重复时
cur == back == target之类的相等把"哪段"判糊了,得退化成l++逐个挪(那是 81.搜索旋转排序数组 II,最坏 O ( n ) O(n) O(n))。面试要能主动说出这个区别。
153.寻找旋转排序数组中的最小值
题目链接 :153.寻找旋转排序数组中的最小值
题目描述 :一个原本升序、元素互不相同 的数组旋转过一次(可能没转)。不要求找到 target ,直接返回数组里的最小值 。要求 O ( log n ) O(\log n) O(logn)。
思路一:和最右元素比段,红蓝收敛到第二段的头
核心思想:
- 旋转数组两段各自升序、第一段整体大于第二段------最小值就是第二段的头 。红蓝二分直接找它:染蓝条件 = "
nums[mid]已在第二段"。 - 判定只比一件事:
nums[mid]和最右元素 (第二段最大值)。nums[mid] > back------mid 在第一段,断崖在右边,mid 染红l = mid;nums[mid] <= back------mid 在第二段(或没旋转),mid 自己可能就是最小,染蓝r = mid。 - 为什么和最右比不和最左比 ?
nums[mid] > nums[0]区分不出"没旋转"(mid 就是最小或其后)和"mid 在第一段"(最小在远处右边)------两种情况方向完全相反。和最右比则每种情况方向唯一,这是本题最巧的一步。 r初始给n - 1(不是n):答案是一个真实存在的元素,最右元素天然"在第二段",蓝侧哨兵从真元素起步即可,出循环nums[r]直接就是最小值。
时间复杂度 : O ( log n ) O(\log n) O(logn),其中 n 为数组长度。
空间复杂度 : O ( 1 ) O(1) O(1)。
java
class Solution {
public int findMin(int[] nums) {
// 一定是两段(或没转),左边的比右边的大,找的就是右边那段的最左边的值
int l = -1, r = nums.length - 1; // r 从真元素起步:nums[n-1] 必在第二段
while (l + 1 < r) {
int mid = l + (r - l) / 2;
if (nums[mid] > nums[nums.length - 1]) {
l = mid; // mid 在第一段:断崖在右边,最小值在 (mid, r]
} else {
r = mid; // mid 在第二段(或没旋转):mid 自己可能就是最小,染蓝
}
}
return nums[r]; // 收敛点 = 第二段第一个元素 = 最小值
}
}
和 33.搜索旋转排序数组 是同一套"旋转数组"框架的两道门:33 用最右元素判target 的位置方向 (六分支),153 用最右元素判mid 的段位 (一个比较)------参照物都是
nums[n-1],一起复习对比着记。
4.寻找两个正序数组的中位数
题目链接 :4.寻找两个正序数组的中位数
题目描述 :给两个升序 数组 nums1、nums2,长度分别是 m、n,返回合并后 的中位数。要求时间复杂度 O ( log ( m + n ) ) O(\log (m+n)) O(log(m+n))。进阶: O ( log min ( m , n ) ) O(\log \min(m, n)) O(logmin(m,n))。
思路一:二分短数组的切点(只查一个不等式,找最后一个合法的 i)
核心思想:
- 中位数把合并数组切成元素个数确定的左右两半 :左半要凑
totalLeft = (m + n + 1) / 2个(奇数时多的放左边,奇偶统一)。切点定了,中位数只由"左半最大值 和右半最小值"决定。 - 合并切缝由两个数组各自的切点拼成:
nums1切i个进左半、nums2切j个进左半,i定了j就被唯一确定 (j = totalLeft - i)------所以只需要在一个数组上二分。在短的那个上二分 :j不会越界(短数组是"调节变量",长数组兜底),这就是 O ( log min ( m , n ) ) O(\log \min(m,n)) O(logmin(m,n)) 的由来。 - 合法的切缝满足交叉不大于 :
nums1[i-1] <= nums2[j]且nums2[j-1] <= nums1[i]。妙处在单调性:i越大nums1[i-1]越大、nums2[j]越小(j随i减小)------第一个不等式对i单调 。于是二分目标是清晰的:找最后一个满足nums1[i-1] <= nums2[j]的i。 - 只查第一个不等式就够吗?够,短数组保证自动成立:取到最大的合法
i后,i + 1处(若存在)必然nums1[i] > nums2[j-1]------恰好就是第二个不等式"反向成立"的形态;i取到边界m时右半为空、正无穷兜底。一个方向的检查隐含了另一个方向,这是本解法最省事的一步。 - 两个工程细节:
mid用上取整 (i = left + (right - left + 1) / 2)------收缩写的是left = i,下取整可能i == left造成死循环,上取整保证i严格大于left;边界用正负无穷 当哨兵------i = 0左半为空取-∞、i = m右半为空取+∞,四侧比较不用特判空。 - 收敛出合法切缝后:奇数总数答案是
max(左半两个最大),偶数总数是(左半最大 + 右半最小) / 2.0。
时间复杂度 : O ( log min ( m , n ) ) O(\log \min(m, n)) O(logmin(m,n)),其中 m、n 为两数组长度,二分范围是短数组的切点。
空间复杂度 : O ( 1 ) O(1) O(1)。
java
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
// 1. 保证 nums1 是更短的数组,二分只在短数组做,减少循环次数
if (nums1.length > nums2.length) {
int[] temp = nums1;
nums1 = nums2;
nums2 = temp;
}
// m:短数组长度,n:长数组长度
int m = nums1.length;
int n = nums2.length;
// 二分左右边界:i 可以取 0 ~ m
int left = 0, right = m;
// 合并后左边一共需要放多少个数字
// 加1是为了奇数时左边多一个,中位数直接取左边最大值
int totalLeft = (m + n + 1) / 2;
// 二分循环,找到最后一个满足 nums1[i-1] <= nums2[j] 的切割点 i
while (left < right) {
// 取中间切割位置,向上取整,避免死循环(left = i 时 i 必须严格大于 left)
int i = left + (right - left + 1) / 2;
// 根据左边总数量,算出 nums2 需要切到 j 位置:i 定了 j 唯一确定
int j = totalLeft - i;
// 分割不合法:nums1 左边最大值 > nums2 右边最小值
// 说明 nums1 切太靠右,数字太大,切割点往左移
if (nums1[i - 1] > nums2[j]) {
right = i - 1;
} else {
// 该不等式成立:i 还可能更大,继续往右试探
left = i;
}
}
// 循环结束 left == right,就是最终切割位置 i(最后一个合法的)
int i = left;
int j = totalLeft - i;
// 处理四种边界情况,切割在数组开头/末尾用无穷值代替
// nums1 左半边最大值
int nums1Left = i == 0 ? Integer.MIN_VALUE : nums1[i - 1];
// nums1 右半边最小值
int nums1Right = i == m ? Integer.MAX_VALUE : nums1[i];
// nums2 左半边最大值
int nums2Left = j == 0 ? Integer.MIN_VALUE : nums2[j - 1];
// nums2 右半边最小值
int nums2Right = j == n ? Integer.MAX_VALUE : nums2[j];
// 判断总长度奇偶,计算中位数
if ((m + n) % 2 == 1) {
// 奇数:左边多一个,中位数是左边两段最大的数
return Math.max(nums1Left, nums2Left);
} else {
// 偶数:左边最大 和 右边最小 的平均值,除以2.0保证浮点
return (Math.max(nums1Left, nums2Left) + Math.min(nums1Right, nums2Right)) / 2.0;
}
}
}
面试可以退一档的写法:对
k = (m+n+1)/2做"二分扔掉第 k 小的一半 "------每轮比较两数组第k/2个元素,扔掉小的那k/2个、k减半, O ( log ( m + n ) ) O(\log (m+n)) O(log(m+n))。好讲好写,但达不到进阶的 O ( log min ( m , n ) ) O(\log \min(m,n)) O(logmin(m,n)),讲完主动补一句"还可以在短数组上二分切点做到 log min \log \min logmin",加分点就到了。
总结:二分篇的主线是把"找什么"定义清楚,再让区间每轮确定地砍半 。35.搜索插入位置 红蓝三分支,
l和r把 target 夹出来,存在命中不存在夹位;74.搜索二维矩阵 右上角 Z 字形每步排掉一行一列,进阶认出矩阵是一维数组蜷起来的、拉平一次二分;34.在排序数组中查找元素的第一个和最后一个位置 同一个红蓝模板改一个比较符跑两遍,≥取r找首、≤取l找尾;33.搜索旋转排序数组 拿最右元素当段判据,同段比值、异段看段位,六分支一个判定;153.寻找旋转排序数组中的最小值 同一个参照只判 mid 的段位,r收敛到第二段的头;4.寻找两个正序数组的中位数 在短数组上二分切点,靠单调性只查一个不等式、上取整防死循环、正负无穷当哨兵。核心都是一句话:答案所在的判定是单调的,每轮确定地丢掉一半------排序只是单调性最常见的一件外衣。