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。

用这一行的第一个元素 matrixmid_row0 和最后一个元素 matrixmid_rown-1 来判断 target 可能在什么位置:

  • 如果 target < matrixmid_row0,目标只能在更上面的行,缩小到上半区。
  • 如果 target > matrixmid_rown-1,目标只能在更下面的行,缩小到下半区。
  • 如果 matrixmid_row0 <= target <= matrixmid_rown-1,说明目标只可能在 mid_row 这一行里。

注意:列数是 n == matrixi.length,最后一列的下标是 n - 1,C 里 matrixColSize0 代表列数,最后一列下标是 matrixColSize0 - 1。

第二层二分:在这一行中二分查找

在列区间 0, n-1 上再做一次普通二分查找:

  • 如果 matrixmid_rowmid_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

这样 matrixrowcol 就对应虚拟一维数组的 mid 位置。

二分过程:

  1. 初始化 left = 0,right = m * n - 1。
  2. 循环条件 left <= right:
    • 取中点 mid = (left + right) / 2。
    • 通过上面的公式计算 row 和 col。
    • 比较 matrixrowcol 与 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。
  • matrixColSize0 通常就是列数 n(题目保证每行长度相同)。
  • 如果有 n 列,那么合法列下标是 0, n-1,最后一列的下标是 n - 1 或 matrixColSize0 - 1。

这点在你写"比较 matrixmid_row0 和这一行最后一个元素"时非常关键,容易写成越界的下标。

总结

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

相关推荐
hanlin0311 小时前
刷题笔记:力扣第32题-最长有效括号
笔记·算法·leetcode
All for pursuit.11 小时前
【动态规划-21】312.戳气球
数据结构·c++·算法·leetcode·动态规划
碧海银沙音频科技研究院13 小时前
基于炬芯ATS3089X自研AEC声学回声消除算法
算法
通问AI14 小时前
在两台 Mac Studio 上跑 800B 模型:MLX + RDMA over Thunderbolt 实测与四道门选型法
算法
ZywOo62314 小时前
《算法竞赛进阶指南》0x03 前缀和与差分
算法
Scintilla_y14 小时前
DAY2 滑动窗口
算法·leetcode·散列表
lvwangshu14 小时前
笛卡尔树
算法·图论
026d2314 小时前
状态信号只知“其然”不知“其所以然”:变工况机械故障诊断的物理盲区
算法
姜鱼问生14 小时前
备份和服务器在同一台机器上,等于没备份
算法·架构
姜鱼问生15 小时前
别再 chmod 777 了,用 ACL 给它开一扇窗
后端·算法·架构