1. 项目概述:LeetCode热题100之搜索二维矩阵
这道题在LeetCode热题100中编号为74,属于中等难度但高频出现的经典题型。题目要求在一个每行从左到右递增、且每行第一个数大于上一行最后一个数的二维矩阵中,高效判断目标值是否存在。这不仅是面试中的常客,更是理解二分查找变体应用的绝佳案例。
我第一次遇到这个问题是在某大厂的技术面,面试官在考察完基础算法后突然抛出这个变种题。当时我的第一反应是直接暴力遍历,但面试官要求时间复杂度必须优于O(m*n)。经过提示才意识到可以将二维矩阵"拍平"为一维数组来处理。这个经历让我深刻体会到:算法题的核心不在于记忆解法,而在于培养将已知方法灵活迁移到新场景的能力。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心算法解析
2.1 二分查找的二维扩展
传统二分查找针对一维有序数组,而本题需要处理的是具有特殊排序规则的二维矩阵。关键发现是:虽然数据存储在二维结构中,但满足以下两个条件时,整体仍然保持一维有序性:
- 每行元素严格递增(左→右)
- 下一行首元素严格大于上一行末元素(上→下)
这种特性使得我们可以将二维坐标(m,n)线性映射为一维坐标:
code复制一维位置 = 当前行号 * 列总数 + 当前列号
反之,给定一维位置pos时:
code复制行号 = pos // 列总数
列号 = pos % 列总数
2.2 时间复杂度对比
| 方法 | 时间复杂度 | 空间复杂度 | 适用场景 |
|---|---|---|---|
| 暴力遍历 | O(m*n) | O(1) | 任何矩阵 |
| 逐行二分 | O(m*logn) | O(1) | 行间无序但行内有序 |
| 全局二分 | O(log(m*n)) | O(1) | 完全有序的二维矩阵 |
| 阶梯搜索 | O(m+n) | O(1) | 行间和行内均有序 |
在本题的特定条件下,全局二分是最优解。但实际面试中,面试官可能会逐步引导你从暴力法开始优化,这正是考察你算法思维的过程。
3. 代码实现与细节处理
3.1 Python标准实现
python复制def searchMatrix(matrix: List[List[int]], target: int) -> bool:
if not matrix or not matrix[0]:
return False
rows, cols = len(matrix), len(matrix[0])
left, right = 0, rows * cols - 1
while left <= right:
mid = (left + right) // 2
row, col = mid // cols, mid % cols
if matrix[row][col] == target:
return True
elif matrix[row][col] < target:
left = mid + 1
else:
right = mid - 1
return False
3.2 关键细节说明
- 边界检查:首先处理空矩阵或空行的特殊情况。这在面试中容易被忽略但非常重要。
- 索引计算:注意
mid // cols得到的是行号,mid % cols是列号。搞反会导致越界。 - 循环条件:使用
left <= right而非left < right以确保边界值被检查。 - 更新策略:根据比较结果更新左右指针时,需要
mid ± 1以避免死循环。
特别注意:Python中的整除运算符
//在负数情况下的表现与其他语言不同。虽然本题不涉及,但在其他变种问题中可能成为坑点。
4. 常见变种与应对策略
4.1 变种1:行有序但行间无序
此时全局二分不再适用,但每行内部仍然有序。解决方案:
- 遍历每一行,对每行进行二分查找
- 先定位可能行(如第一列),再在行内搜索
python复制# 方法1:逐行二分
def searchMatrix_variant1(matrix, target):
for row in matrix:
idx = bisect.bisect_left(row, target)
if idx < len(row) and row[idx] == target:
return True
return False
4.2 变种2:行列分别有序但整体无序
即每行从左到右递增,每列从上到下递增,但下一行首元素不一定大于上一行末元素。此时可以采用:
- 阶梯搜索法:从右上角开始,类似二叉搜索树的遍历
- 四分治法:将矩阵分为四个象限递归处理
阶梯搜索实现:
python复制def searchMatrix_variant2(matrix, target):
if not matrix:
return False
row, col = 0, len(matrix[0]) - 1
while row < len(matrix) and col >= 0:
if matrix[row][col] == target:
return True
elif matrix[row][col] < target:
row += 1
else:
col -= 1
return False
5. 调试技巧与测试用例设计
5.1 必须覆盖的测试场景
- 空矩阵输入
- 单元素矩阵(包含/不包含目标值)
- 目标值在矩阵四个角落的情况
- 目标值小于所有元素/大于所有元素
- 奇数行偶数列/偶行列数的矩阵
- 存在重复元素时的行为
5.2 调试日志技巧
在二分查找中加入打印语句,帮助理解搜索过程:
python复制while left <= right:
mid = (left + right) // 2
row, col = mid // cols, mid % cols
print(f"Searching range [{left}, {right}], mid={mid} -> matrix[{row}][{col}]={matrix[row][col]}")
...
典型错误案例:
- 忘记处理空输入导致IndexError
- 行列计算错误导致越界
- 循环条件错误导致漏检或死循环
- 更新指针时忘记±1导致死循环
6. 性能优化与语言特性
6.1 Python中的bisect模块
虽然标准二分写法通用,但Python的bisect模块可以提供更简洁的实现:
python复制import bisect
def searchMatrix_optimized(matrix, target):
if not matrix:
return False
# 先定位行
first_col = [row[0] for row in matrix]
row_idx = bisect.bisect_right(first_col, target) - 1
if row_idx < 0:
return False
# 在行内搜索
col_idx = bisect.bisect_left(matrix[row_idx], target)
return col_idx < len(matrix[row_idx]) and matrix[row_idx][col_idx] == target
6.2 其他语言实现要点
- C++:注意vector的empty()判断和size()返回size_t类型
- Java:二维数组的length属性需要分别获取行数和列数
- JavaScript:注意浮点数除法与位运算的性能差异
7. 实际工程中的应用场景
虽然看似纯粹的算法题,但这种二维搜索的思想在以下场景有实际应用:
- 地理信息系统中的区域查询
- 游戏开发中的地图区块加载
- 数据库索引的多维范围查询
- 图像处理中特定像素值的快速定位
例如在电商系统中,商品可能按价格和评分两个维度存储,需要快速找到符合特定价格区间和评分要求的商品,这就类似于二维搜索问题的变种。
8. 学习路径建议
要系统掌握这类问题,建议按照以下顺序练习:
- 标准二分查找(LeetCode 704)
- 搜索插入位置(35)
- 在排序数组中查找元素的第一个和最后一个位置(34)
- 搜索旋转排序数组(33)
- 寻找峰值(162)
- 二维矩阵搜索(本题)
- 行列分别有序的矩阵搜索(240)
这种渐进式的练习可以帮助你理解二分查找的各种变体,培养将一维算法扩展到高维的能力。
