LeetCode 4. 寻找两个正序数组的中位数 ------ 二分划分的艺术
这道题是 LeetCode 前 10 题中唯一的 Hard,也是面试高频题。它的要求很直白------给两个有序数组,求中位数。朴素做法 O(m+n) 写完只需 2 分钟,但题目要求 O(log(m+n)),这就把难度直接拉满。
O(log) 意味着什么?二分查找。但二分什么?怎么二分?这才是本题的核心智慧:
- 中位数的本质不是"找第 k 小的数",而是把两个数组划分成数量相等的左右两半,使得左半的最大值 ≤ 右半的最小值
- 我们不需要对两个数组一起二分------只对较短的数组二分即可,另一个数组的分割位置由数学公式直接算出
- 边界处理是本题最大的坑:分割线可能划在数组的最左端(左边没有元素)或最右端(右边没有元素),用
-∞和+∞优雅处理
本文从暴力合并 → 双指针计数 → 二分划分层层递进,重点讲解二分划分的核心逻辑和边界处理,附 JavaScript/Python 双版本代码 + 完整推演。
前置阅读:建议先熟悉二分查找的基本模板(while left <= right 和 while left < right 两种写法),以及"第 k 小"类问题的通用思路。
问题描述
LeetCode 4. Median of Two Sorted Arrays(寻找两个正序数组的中位数)
给定两个大小分别为
m和n的正序(从小到大)数组nums1和nums2。请你找出并返回这两个正序数组的 中位数。要求:算法的时间复杂度为 O(log(m+n))。
示例 1:
css
输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3],中位数 2
示例 2:
ini
输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4],中位数 (2 + 3) / 2 = 2.5
中位数的数学定义:
scss
总长度 m+n 为奇数 → 中位数 = 第 ⌊(m+n)/2⌋ + 1 个元素(下标从 1 开始)
总长度 m+n 为偶数 → 中位数 = (第 (m+n)/2 个 + 第 (m+n)/2 + 1 个) / 2
图示(示例 1):
ini
nums1: [1, 3]
\ /
nums2: [ 2 ]
/ \
合并: [1, 2, 3] → 中位数 = 2
约束条件:
nums1.length == mnums2.length == n0 <= m <= 10000 <= n <= 10001 <= m + n <= 2000-10^6 <= nums1[i], nums2[i] <= 10^6
核心思想
为什么不能用简单合并?
合并两个有序数组只需要 O(m+n) 时间,这就是归并排序中 merge 函数的逻辑:
scss
双指针逐一比较 → 合并成一个有序数组 → 取中间元素
时间 O(m+n),空间 O(m+n)
但这不符合 O(log(m+n)) 的要求。题目卡的就是这个 log------必须用二分。
中位数的本质:划分,而非查找
很多人的第一反应是"找第 k 小的数",这个思路可行(二分删减),但更优雅的理解是划分视角:
ini
中位数 = 将两个数组划分成左右两半的分割线
左半部分(共 (m+n+1)/2 个元素) 右半部分(共 (m+n)/2 个元素)
┌─────────────────────────┐ ┌─────────────────────────┐
│ nums1[0..i-1] │ │ nums1[i..m-1] │
│ + │ │ + │
│ nums2[0..j-1] │ │ nums2[j..n-1] │
└─────────────────────────┘ └─────────────────────────┘
条件:左半最大值 ≤ 右半最小值
设 i = nums1 的分割位置(分割线后第一个元素的索引)
设 j = nums2 的分割位置(分割线后第一个元素的索引)
左半元素数 = i + j = (m + n + 1) / 2(整数除法,保证左半 ≥ 右半)
核心不等式(划分合法的条件):
css
nums1[i-1] ≤ nums2[j] ← nums1 左半最大值 ≤ nums2 右半最小值
nums2[j-1] ≤ nums1[i] ← nums2 左半最大值 ≤ nums1 右半最小值
如果这两个条件都满足,我们就找到了正确的划分!
如果 nums1i-1 > nums2j ,说明 nums1 左半有太大的元素,i 应该左移 (减小)。 如果 nums2j-1 > nums1i ,说明 nums2 左半有太大的元素,i 应该右移(增大 nums1 左半,间接减小 nums2 左半)。
数据结构设计
ini
二分搜索 i 的过程:
在 nums1 上二分 i 的位置(0 ≤ i ≤ m)
j = (m + n + 1) / 2 - i ← 由恒等式直接算出
┌──────────────────────────────────────┐
│ nums1: [ ... i-1 │ i ... ] │
│ nums2: [ ... j-1 │ j ... ] │
│ │
│ leftMax1 = nums1[i-1] (或 -∞) │
│ rightMin1 = nums1[i] (或 +∞) │
│ leftMax2 = nums2[j-1] (或 -∞) │
│ rightMin2 = nums2[j] (或 +∞) │
└──────────────────────────────────────┘
边界处理:
当 i=0 时,leftMax1 = -∞(nums1 左半为空)
当 i=m 时,rightMin1 = +∞(nums1 右半为空)
当 j=0 时,leftMax2 = -∞(nums2 左半为空)
当 j=n 时,rightMin2 = +∞(nums2 右半为空)
💡 为什么只对较短的数组二分 :确保
j = (m+n+1)/2 - i永远在[0, n]范围内。如果对长数组二分,j 可能溢出负数或超出 n。
思路分析
解法一:暴力合并 ⭐(新手友好,不满足要求)
核心思路:直接用归并排序的 merge 操作合并两个有序数组,然后取中位数。
less
merge(nums1, nums2):
双指针 i, j 从 0 开始
每次取 nums1[i] 和 nums2[j] 中较小的放入结果数组
合并完成后,取 merged[(m+n-1)/2] 和 merged[(m+n)/2] 的平均值
⚠️ 时间 O(m+n),空间 O(m+n),不符合题目 O(log(m+n)) 要求。面试中作为破冰思路提一下即可。
解法二:双指针计数 ⭐(不创建新数组)
核心思路:不真的合并,而是用两个指针遍历,走到"中位数位置"就取出来。
markdown
不需要保存整个合并数组,只需要知道:
- 第 (m+n-1)/2 个元素(中位数左侧)
- 第 (m+n)/2 个元素 (中位数右侧)
遍历过程中,每取一个元素,计数 +1。计数到达目标位置时记录该元素。
⚠️ 时间 O(m+n) 依然不符合 log 要求,但空间优化到 O(1),且思路为最优解做了铺垫。
解法三:二分划分 ⭐⭐⭐(最优解,O(log min(m,n)))
核心思路 :在较短数组上二分搜索分割线位置 i,另一个数组的分割线 j 由恒等式直接算出。检查划分是否满足条件,满足即找到中位数。
css
二分搜索 i ∈ [0, m](m 是较短数组长度):
j = (m + n + 1) / 2 - i
如果 nums1[i-1] > nums2[j]: → i 太大,往左搜(right = i - 1)
如果 nums2[j-1] > nums1[i]: → i 太小,往右搜(left = i + 1)
否则: → 找到正确划分!
找到正确划分后,中位数计算:
- 总长奇数:中位数 = max(leftMax1, leftMax2)
- 总长偶数:中位数 = (max(leftMax1, leftMax2) + min(rightMin1, rightMin2)) / 2
💡 关键理解 :
j = (m+n+1)/2 - i这个公式保证了左半部分始终有 (m+n+1)/2 个元素 。其中国际惯例用+1确保奇数时左半多一个,这样中位数就是左半的最大值。
三种方法对比
scss
暴力合并(O(m+n), O(m+n)):
nums1 ─┬─ merge ─► [完整有序数组] ─► 取中间
nums2 ─┘
问题:创建了完整数组,空间浪费
双指针计数(O(m+n), O(1)):
nums1 ─┬─ 虚拟遍历 ─► 计数到中位 ─► 返回元素
nums2 ─┘
问题:仍然需要遍历到中间位置
二分划分(O(log min(m,n)), O(1)):
nums1 ─┬─ 二分 i ─► 检查划分 ─► 计算中位数
nums2 ─┘ j 由公式算出
优势:跳过了大部分元素,直接定位分割线
代码实现
JavaScript 版本
方法一:暴力合并
javascript
/**
* @param {number[]} nums1
* @param {number[]} nums2
* @return {number}
*/
var findMedianSortedArrays = function(nums1, nums2) {
const m = nums1.length, n = nums2.length;
const merged = [];
let i = 0, j = 0;
// 归并合并
while (i < m && j < n) {
if (nums1[i] <= nums2[j]) {
merged.push(nums1[i++]);
} else {
merged.push(nums2[j++]);
}
}
while (i < m) merged.push(nums1[i++]);
while (j < n) merged.push(nums2[j++]);
const total = m + n;
if (total % 2 === 1) {
return merged[Math.floor(total / 2)];
} else {
return (merged[total / 2 - 1] + merged[total / 2]) / 2;
}
};
方法二:双指针计数
javascript
var findMedianSortedArrays = function(nums1, nums2) {
const m = nums1.length, n = nums2.length;
const total = m + n;
// 需要到达的位置
const target1 = Math.floor((total - 1) / 2); // 左中位数位置
const target2 = Math.floor(total / 2); // 右中位数位置
let i = 0, j = 0, count = 0;
let val1 = 0, val2 = 0;
while (count <= target2) {
let cur;
if (i < m && (j >= n || nums1[i] <= nums2[j])) {
cur = nums1[i++];
} else {
cur = nums2[j++];
}
if (count === target1) val1 = cur;
if (count === target2) val2 = cur;
count++;
}
return (val1 + val2) / 2;
};
方法三:二分划分 ⭐⭐⭐(最优解)
javascript
/**
* @param {number[]} nums1
* @param {number[]} nums2
* @return {number}
*/
var findMedianSortedArrays = function(nums1, nums2) {
// 确保 nums1 是较短的数组,减少二分搜索范围
if (nums1.length > nums2.length) {
[nums1, nums2] = [nums2, nums1];
}
const m = nums1.length;
const n = nums2.length;
const halfLen = Math.floor((m + n + 1) / 2); // 左半部分应有的元素数
// 在 nums1 上二分搜索分割线 i
let left = 0, right = m;
while (left <= right) {
const i = Math.floor((left + right) / 2); // nums1 的分割线
const j = halfLen - i; // nums2 的分割线(由恒等式算出)
// 四边界值,处理数组边界(用 ±Infinity 哨兵)
const leftMax1 = i === 0 ? -Infinity : nums1[i - 1];
const rightMin1 = i === m ? Infinity : nums1[i];
const leftMax2 = j === 0 ? -Infinity : nums2[j - 1];
const rightMin2 = j === n ? Infinity : nums2[j];
if (leftMax1 <= rightMin2 && leftMax2 <= rightMin1) {
// ✅ 找到正确的划分!
if ((m + n) % 2 === 1) {
// 总长奇数:中位数 = 左半最大值
return Math.max(leftMax1, leftMax2);
} else {
// 总长偶数:中位数 = (左半最大值 + 右半最小值) / 2
return (
Math.max(leftMax1, leftMax2) +
Math.min(rightMin1, rightMin2)
) / 2;
}
} else if (leftMax1 > rightMin2) {
// nums1 左半有太大元素 → i 需要左移
right = i - 1;
} else {
// leftMax2 > rightMin1 → nums2 左半有太大元素 → i 需要右移
left = i + 1;
}
}
// 理论上不会到达这里(题目保证输入有效)
throw new Error('输入数组不是有序的');
};
Python 版本
方法一:暴力合并
python
def findMedianSortedArrays(nums1: list[int], nums2: list[int]) -> float:
"""方法一:暴力合并"""
merged = []
i = j = 0
m, n = len(nums1), len(nums2)
while i < m and j < n:
if nums1[i] <= nums2[j]:
merged.append(nums1[i])
i += 1
else:
merged.append(nums2[j])
j += 1
while i < m:
merged.append(nums1[i])
i += 1
while j < n:
merged.append(nums2[j])
j += 1
total = m + n
if total % 2 == 1:
return merged[total // 2]
else:
return (merged[total // 2 - 1] + merged[total // 2]) / 2
方法二:双指针计数
python
def findMedianSortedArrays(nums1: list[int], nums2: list[int]) -> float:
"""方法二:双指针计数"""
m, n = len(nums1), len(nums2)
total = m + n
target1 = (total - 1) // 2
target2 = total // 2
i = j = count = 0
val1 = val2 = 0
while count <= target2:
if i < m and (j >= n or nums1[i] <= nums2[j]):
cur = nums1[i]
i += 1
else:
cur = nums2[j]
j += 1
if count == target1:
val1 = cur
if count == target2:
val2 = cur
count += 1
return (val1 + val2) / 2
方法三:二分划分 ⭐⭐⭐(最优解)
python
def findMedianSortedArrays(nums1: list[int], nums2: list[int]) -> float:
"""方法三:二分划分,O(log min(m,n))"""
# 确保 nums1 是较短的数组
if len(nums1) > len(nums2):
nums1, nums2 = nums2, nums1
m, n = len(nums1), len(nums2)
half_len = (m + n + 1) // 2 # 左半部分应有的元素数
left, right = 0, m
while left <= right:
i = (left + right) // 2 # nums1 的分割线
j = half_len - i # nums2 的分割线
# 四边界值,用 float('inf') 和 float('-inf') 做哨兵
left_max1 = nums1[i - 1] if i > 0 else float('-inf')
right_min1 = nums1[i] if i < m else float('inf')
left_max2 = nums2[j - 1] if j > 0 else float('-inf')
right_min2 = nums2[j] if j < n else float('inf')
if left_max1 <= right_min2 and left_max2 <= right_min1:
# 找到正确的划分
if (m + n) % 2 == 1:
return max(left_max1, left_max2)
else:
return (
max(left_max1, left_max2) +
min(right_min1, right_min2)
) / 2
elif left_max1 > right_min2:
# nums1 左半有太大元素 → i 左移
right = i - 1
else:
# nums2 左半有太大元素 → i 右移(间接减小 nums2 左半)
left = i + 1
raise ValueError("输入数组不是有序的")
逐步推演
二分划分法完整推演(示例 1)
以 nums1 = [1, 3], nums2 = [2] 为例,逐步展示二分搜索过程:
ini
nums1 = [1, 3] 长度 m = 2
nums2 = [2] 长度 n = 1
总长 = 3 (奇数)
halfLen = (3 + 1) / 2 = 2 ← 左半需要 2 个元素
═══════════════ 初始化 ═══════════════
left = 0, right = 2 (m)
═══════════════ 第 1 轮二分 ═══════════════
i = (0 + 2) / 2 = 1
j = halfLen - i = 2 - 1 = 1
划分状态:
nums1: [ 1 │ 3 ]
↑ i=1
nums2: [ 2 │ ]
↑ j=1
计算边界值:
leftMax1 = nums1[0] = 1 (i=1, i>0 ✓)
rightMin1 = nums1[1] = 3 (i=1, i<2 ✓)
leftMax2 = nums2[0] = 2 (j=1, j>0 ✓)
rightMin2 = +∞ (j=1, j==n, 哨兵)
检查条件:
leftMax1 (1) ≤ rightMin2 (+∞) ✅
leftMax2 (2) ≤ rightMin1 (3) ✅
🎯 找到正确划分!
总长 3 是奇数 → 中位数 = max(leftMax1, leftMax2) = max(1, 2) = 2
返回 2.0 ✅
二分划分法完整推演(示例 2)
以 nums1 = [1, 2], nums2 = [3, 4] 为例:
ini
nums1 = [1, 2] 长度 m = 2
nums2 = [3, 4] 长度 n = 2
总长 = 4 (偶数)
halfLen = (4 + 1) / 2 = 2 ← 左半需要 2 个元素
═══════════════ 初始化 ═══════════════
left = 0, right = 2
═══════════════ 第 1 轮二分 ═══════════════
i = (0 + 2) / 2 = 1
j = 2 - 1 = 1
划分状态:
nums1: [ 1 │ 2 ]
↑ i=1
nums2: [ 3 │ 4 ]
↑ j=1
计算边界值:
leftMax1 = nums1[0] = 1
rightMin1 = nums1[1] = 2
leftMax2 = nums2[0] = 3
rightMin2 = nums2[1] = 4
检查条件:
leftMax1 (1) ≤ rightMin2 (4) ✅
leftMax2 (3) ≤ rightMin1 (2) ❌ 3 > 2!
nums2 左半有太大的元素 → i 需要右移
left = i + 1 = 2
═══════════════ 第 2 轮二分 ═══════════════
i = (2 + 2) / 2 = 2
j = 2 - 2 = 0
划分状态:
nums1: [ 1, 2 │ ]
↑ i=2
nums2: [ │ 3, 4 ]
↑ j=0
计算边界值:
leftMax1 = nums1[1] = 2 (i=2, i>0 ✓)
rightMin1 = +∞ (i=2, i==m, 哨兵)
leftMax2 = -∞ (j=0, j==0, 哨兵)
rightMin2 = nums2[0] = 3 (j=0, j<n ✓)
检查条件:
leftMax1 (2) ≤ rightMin2 (3) ✅
leftMax2 (-∞) ≤ rightMin1 (+∞) ✅
🎯 找到正确划分!
总长 4 是偶数 → 中位数 = (max(左) + min(右)) / 2
= (max(2, -∞) + min(+∞, 3)) / 2
= (2 + 3) / 2
= 2.5 ✅
边界情况推演:一个数组为空
以 nums1 = [], nums2 = [1] 为例:
ini
nums1 = [] 长度 m = 0
nums2 = [1] 长度 n = 1
总长 = 1 (奇数)
halfLen = (1 + 1) / 2 = 1
left = 0, right = 0
i = 0, j = 1 - 0 = 1
划分状态:
nums1: [ │ ] ← 空数组,分割线在最左端
↑ i=0
nums2: [ 1 │ ]
↑ j=1
计算边界值:
leftMax1 = -∞ (i=0, 哨兵)
rightMin1 = +∞ (i=0, i==m=0, 哨兵)
leftMax2 = nums2[0] = 1 (j=1, j>0 ✓)
rightMin2 = +∞ (j=1, j==n, 哨兵)
检查条件:
leftMax1 (-∞) ≤ rightMin2 (+∞) ✅
leftMax2 (1) ≤ rightMin1 (+∞) ✅
🎯 找到正确划分!
总长 1 是奇数 → 中位数 = max(-∞, 1) = 1
返回 1.0 ✅
💡 哨兵
-∞和+∞的妙用 :它们让边界情况的比较逻辑和正常情况完全统一------空半边的"最大值"是-∞(永远不会大于对面),"最小值"是+∞(永远不会小于对面)。不需要写任何 if-else 特判。
复杂度分析
| 方法 | 时间复杂度 | 空间复杂度 | 优点 | 缺点 |
|---|---|---|---|---|
| 暴力合并 | O(m+n) | O(m+n) | 思路最直观,一行 merge 搞定 | 空间和时间都不满足要求 |
| 双指针计数 | O(m+n) | O(1) | 不创建额外数组 | 仍需遍历到中位位置,O(n) 不满足要求 |
| 二分划分 ⭐⭐⭐ | O(log min(m,n)) | O(1) | 最优解,完全符合要求 | 边界处理需要细心,理解门槛较高 |
- 时间复杂度:二分划分法每次迭代排除一半的搜索空间(在较短数组上),所以是 O(log min(m,n))。这是本题能达到的理论下限
- 空间复杂度:二分划分只用了常数个变量(left, right, i, j, 四个边界值),O(1)
- 稳定性:中位数计算本身不涉及元素顺序改变,三种方法结果完全一致
二分搜索过程分解
css
以 m=4, n=6 为例:
二分搜索空间大小: m+1 = 5 个可能的 i 值 {0, 1, 2, 3, 4}
最多需要 log₂(5) ≈ 3 次迭代
每次迭代的工作量:
计算 j : O(1)
取四个边界值 : O(1)(数组随机访问)
判断条件 : O(1)
─────────────────────
每轮耗时 O(1),共 O(log min(m,n)) 轮
与 O(m+n) 的对比(m=n=1000):
O(m+n) ≈ 2000 步
O(log min(m,n)) ≈ log₂(1000) ≈ 10 步
快了 200 倍!
举一反三
本题在"有序数组查找"体系中的位置
arduino
有序数组中的二分查找体系:
LeetCode 4. 寻找两个正序数组的中位数(Hard)
├── 基础:二分查找 → LeetCode 704
├── 变体:搜索旋转排序数组 → LeetCode 33, 81
├── 扩展:两个有序数组的第 K 小 → 本题的"第K小"视角
│ └── 本质:每次排除 k/2 个元素(二分删减法)
└── 应用:区间检索、数据库索引合并、流式中位数
关联题目
| 题目 | 核心思想 | 与本题的关系 |
|---|---|---|
| 704. 二分查找 | 经典二分模板 | 二分搜索的基础 |
| 33. 搜索旋转排序数组 | 二分 + 有序半段判定 | 二分的变体------不是全局有序但可以二分 |
| 240. 搜索二维矩阵 II | 从右上角开始,每次排除一行或一列 | 每次排除一部分的二分思想 |
| 295. 数据流的中位数 | 双堆(最大堆+最小堆)维护中位数 | 中位数的动态版本------数据流场景 |
| 658. 找到 K 个最接近的元素 | 二分 + 滑动窗口 | 二分定位 + 窗口扩展 |
| 1095. 山脉数组中查找目标值 | 三分查找峰顶 + 二分查找 | 先找峰,再分两段二分 |
"第 K 小"通用视角(题外拓展)
本题也可以从"求两个有序数组的第 K 小元素"的角度解决:
ini
思路:每次比较 nums1[k/2-1] 和 nums2[k/2-1]
- 较小的那个数组的前 k/2 个元素不可能是第 k 小的
- 排除它们,k 减去排除的数量,继续递归
递推公式:k = (m+n+1)/2(奇数)或 k = (m+n)/2, (m+n)/2+1(偶数)
findKth(nums1, nums2, k):
1. 如果 nums1 为空 → 返回 nums2[k-1]
2. 如果 k == 1 → 返回 min(nums1[0], nums2[0])
3. 比较 nums1[k/2-1] 和 nums2[k/2-1]
→ 删除较小数组的前 k/2 个元素
→ 递归 findKth(剩余, nums2, k - 删除数量)
但二分划分法更加优雅------把"找第 k 小"转化为"划分"问题,边界处理用哨兵统一。这两种视角是等价的,理解其一即可。
延伸思考
在实际面试中,面试官可能追问:
-
"为什么是 O(log min(m,n)) 而不是 O(log(m+n))?" → 因为我们只在较短数组上二分搜索,搜索空间是 min(m,n)+1 个可能位置。对短数组二分就已经能确定整个划分了,不需要对两个数组都二分
-
"halfLen 公式里的
+1是什么意思?" →(m+n+1)/2中的+1让奇数总长时左半多分一个元素。这样中位数就是max(左半最大值),不用再去右半找。这是一个让代码统一处理奇偶的技巧 -
"为什么 i 的范围是 0, m 而不是 0, m-1?" → i 是分割线位置(分割线后第一个元素的索引),
i=0表示 nums1 全部在右半,i=m表示全部在左半。所以一共 m+1 个可能位置 -
"哨兵
±∞不会在比较时出错吗?" → 不会。-∞和任何真实数比较都是更小,+∞和任何真实数比较都是更大,拿它们做 max/min 的结果就是真实数。这正是我们想要的行为------空半边不影响结果 -
"如果两个数组都很大(百万级别),这个算法还能用吗?" → 可以。二分划分只需要 O(log min(m,n)) 次迭代,百万级别的 log 也只有约 20 次。但需要注意整数溢出------
halfLen = (m+n+1)/2在极端情况下可能溢出,实际中用m + Math.floor((n - m + 1) / 2)更安全
总结
| 题目 | 难度 | 核心思想 | 推荐解法 |
|---|---|---|---|
| 4. 寻找两个正序数组的中位数 | 🔴 Hard | 二分划分、哨兵边界、中位数本质 | 二分划分法 |
这道题的核心智慧有三层:
| 层次 | 理解 | 关键点 |
|---|---|---|
| 第一层 | 中位数不是"找第 k 小",而是"划分" | 划分左右两半,左 ≤ 右 |
| 第二层 | 只对短数组二分 | j = halfLen - i 自动算出另一个分割线 |
| 第三层 | 哨兵 ±∞ 统一边界 |
不需要为边界情况写特判 |
一句话口诀 :短数组上二分 i,j 由公式自动算,四边界值做哨兵,交叉比较判合法。左半最大是奇数答,最大最小平均是偶数答。
记住一件事:二分查找不只是在"一个有序数组"里找目标值。本题告诉你------二分同样可以在两个有序数组上找"分割线",而这种"条件判定 + 二分搜索空间"的模式是二分查找最强大的推广。掌握它,一大类 Hard 问题都能迎刃而解。
关于作者:LeetCode 刷题中,致力于用最清晰的方式讲透算法题。欢迎在评论区交流讨论!
相关题解 :33. 搜索旋转排序数组(二分变体) | 295. 数据流的中位数(动态中位数) | 240. 搜索二维矩阵 II(二分排除思想) | 二分专题持续更新中,关注不迷路。