LeetCode 74. Search a 2D Matrix

题目给了一个 m×n 的整数矩阵 matrix,满足两个条件:

  1. 每一行从左到右非递减排序。
  2. 下一行的第一个元素严格大于上一行的最后一个元素。

再给一个整数 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 位置。

二分过程:

  1. 初始化 left = 0,right = m * n - 1。
  2. 循环条件 left <= right:
    • 取中点 mid = (left + right) / 2。
    • 通过上面的公式计算 row 和 col。
    • 比较 matrix[row][col] 与 target:
      • 相等直接返回 true。
      • 当前值小于 target,说明目标在右半区,令 left = mid + 1。
      • 当前值大于 target,目标在左半区,令 right = mid - 1。
  3. 循环结束还没返回,说明没找到,返回 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] 和这一行最后一个元素"时非常关键,容易写成越界的下标。

总结

整体来说,你一开始提出的"先锁定行,再二分列"的思路在大方向上是完全正确的,只需要在边界和下标上更严谨,同时理解官方常用的一维化写法,可以在面试中灵活切换表达。

相关推荐
Kiri霧6 小时前
Go数据类型介绍
java·算法·golang
Mxsoft6197 小时前
AR远程定位偏差救场!某次现场故障,SLAM算法精准对齐设备模型!
算法·ar
liu****7 小时前
一.脚手架介绍以及部分工具使用
开发语言·数据结构·c++·手脚架开发
Hello娃的7 小时前
【量子力学】Hohenberg-Kohn 定理
人工智能·算法
老马啸西风7 小时前
成熟企业级技术平台 MVE-010-IGA(Identity Governance & Administration,身份治理与管理)平台
人工智能·深度学习·算法·职场和发展
老马啸西风8 小时前
成熟企业级技术平台 MVE-010-app 管理平台
人工智能·深度学习·算法·职场和发展
lzh_200110128 小时前
树状数组理解
算法
历程里程碑8 小时前
C++ 6 :string类:高效处理字符串的秘密
c语言·开发语言·数据结构·c++·笔记·算法·排序算法