LeetCode 74. Search a 2D Matrix
题目给了一个 m×n 的整数矩阵 matrix,满足两个条件:
- 每一行从左到右非递减排序。
- 下一行的第一个元素严格大于上一行的最后一个元素。
再给一个整数 target,要求判断 target 是否存在于矩阵中,存在返回 true,否则返回 false,并且时间复杂度要达到 O(log(m×n))。
从这两个性质可以看出,如果把整个矩阵按行顺序读成一维数组,这个"虚拟的一维数组"是有序的,非常适合用二分查找。
初始思路:先确定行,再二分列
一个很自然的想法是分两步来做:
第一层二分:在行上二分查找
维护两个指针 top 和 bottom 指向行号范围 [0, m-1]。
取中间行 mid_row = (top + bottom) / 2。
用这一行的第一个元素 matrix[mid_row][0] 和最后一个元素 matrix[mid_row][n-1] 来判断 target 可能在什么位置:
- 如果 target < matrix[mid_row][0],目标只能在更上面的行,缩小到上半区。
- 如果 target > matrix[mid_row][n-1],目标只能在更下面的行,缩小到下半区。
- 如果 matrix[mid_row][0] <= target <= matrix[mid_row][n-1],说明目标只可能在 mid_row 这一行里。
注意:列数是 n == matrix[i].length,最后一列的下标是 n - 1,C 里 matrixColSize[0] 代表列数,最后一列下标是 matrixColSize[0] - 1。
第二层二分:在这一行中二分查找
在列区间 [0, n-1] 上再做一次普通二分查找:
- 如果 matrix[mid_row][mid_col] == target,返回 true。
- 如果当前值大于 target,移动右边界;小于则移动左边界。
- 如果查完这一行都没找到,则返回 false。
这个两层二分的时间复杂度是 O(log m + log n),和 O(log(m×n)) 同一个量级,完全满足题目的要求。
官方推荐思路:把矩阵当成一维数组二分
在官方题解和很多教程中,更常见的一种写法是直接把整个矩阵看作一个有序的一维数组,在这个一维空间上做一次二分。思想如下:
核心思路
- 矩阵整体有 m 行、n 列,总元素个数是 m * n。
- 把这 m * n 个元素看成一维有序数组,下标从 0 到 m * n - 1。
- 对这个一维下标区间 [0, m*n-1] 做标准二分查找。
- 关键在于:如何把"一维下标"映射回"二维坐标"。
映射关系:
假设一维下标为 mid,则
- 行号 row = mid / n
- 列号 col = mid % n
这样 matrix[row][col] 就对应虚拟一维数组的 mid 位置。
二分过程:
- 初始化 left = 0,right = m * n - 1。
- 循环条件 left <= right:
- 取中点 mid = (left + right) / 2。
- 通过上面的公式计算 row 和 col。
- 比较 matrix[row][col] 与 target:
- 相等直接返回 true。
- 当前值小于 target,说明目标在右半区,令 left = mid + 1。
- 当前值大于 target,目标在左半区,令 right = mid - 1。
- 循环结束还没返回,说明没找到,返回 false。
时间复杂度是一次二分,长度为 m*n,所以是 O(log(m×n)),空间复杂度 O(1)。
两种方法对比
| 方案 | 思路说明 | 时间复杂度 | 代码实现感受 | 典型使用场景 |
|---|---|---|---|---|
| 方法一:两层二分 | 先在行上二分,找到可能的行,再在行内二分 | O(log m + log n) | 容易和"行、列"思路对应 | 便于讲解"如何利用行首和行尾界定范围" |
| 方法二:一维化二分 | 把矩阵当成长度为 m*n 的一维有序数组 | O(log(m×n)) | 代码非常简洁,逻辑集中 | 官方题解、刷题模板、库式写法常用 |
从刷题和复用二分模板的角度,很多人会更推荐"整体当一维数组"的方法;从直观理解矩阵结构的角度,两层二分也非常适合初学者。两种方法本质上利用的是同一个有序性,只是视角不同。
关于 n、matrixColSize 和下标
在 C 的函数签名中,通常会有类似参数:int** matrix, int matrixSize, int* matrixColSize。
- matrixSize 就是行数 m。
- matrixColSize[0] 通常就是列数 n(题目保证每行长度相同)。
- 如果有 n 列,那么合法列下标是 [0, n-1],最后一列的下标是 n - 1 或 matrixColSize[0] - 1。
这点在你写"比较 matrix[mid_row][0] 和这一行最后一个元素"时非常关键,容易写成越界的下标。
总结
整体来说,你一开始提出的"先锁定行,再二分列"的思路在大方向上是完全正确的,只需要在边界和下标上更严谨,同时理解官方常用的一维化写法,可以在面试中灵活切换表达。