langShiftlangShift

矩阵(题单顺序)

热题 100 - 矩阵分组(按题单顺序):73 / 54 / 48 / 240

本页包含题单「矩阵」分组的全部题目,顺序与 list.json 保持一致。


73. 矩阵置零

力扣原题:73. 矩阵置零
Medium
数组
哈希表
矩阵

给定一个 m x n 的矩阵,如果一个元素为 0 ,则将其所在行和列的所有元素都设为 0 。请使用 原地 算法

 

示例 1:

输入:matrix = [[1,1,1],[1,0,1],[1,1,1]]
输出:[[1,0,1],[0,0,0],[1,0,1]]

示例 2:

输入:matrix = [[0,1,2,0],[3,4,5,2],[1,3,1,5]]
输出:[[0,0,0,0],[0,4,5,0],[0,3,1,0]]

 

提示:

  • m == matrix.length
  • n == matrix[0].length
  • 1 <= m, n <= 200
  • -231 <= matrix[i][j] <= 231 - 1

 

进阶:

  • 一个直观的解决方案是使用  O(mn) 的额外空间,但这并不是一个好的解决方案。
  • 一个简单的改进方案是使用 O(m + n) 的额外空间,但这仍然不是最好的解决方案。
  • 你能想出一个仅使用常量空间的解决方案吗?
最优解(原地标记:用第一行/第一列当“记号板”)
正在加载编辑器...
最优解讲解(通俗版 + 推理过程)
  • 题意翻译:只要某个格子是 0,就要把它所在整行整列都变成 0,并且要原地做。
  • 朴素做法的坑:你如果边扫边改,一旦把某个格子改成 0,会“污染”后续判断,导致清零范围无限扩大。
  • 正确做法必须分两步
    1. 先“记录”哪些行/列需要清零
    2. 再统一执行清零
  • 为什么不用额外数组也能记录?:把第一行/第一列当“记号板”:
    • 当发现 matrix[i][j]==0,就做标记:matrix[i][0]=0(第 i 行要清零)、matrix[0][j]=0(第 j 列要清零)。
  • 但是第一行/第一列本身怎么办?:它们既当数据又当标记,必须单独记录:
    • firstRowZero / firstColZero 先记住第一行/第一列是否原本就含 0。
  • 执行顺序为什么重要?
    • 标记阶段从 (1,1) 开始,避免直接覆盖第一行/列的“是否清零信息”。
    • 清零阶段也从 (1,1) 开始,最后再看 firstRowZero/firstColZero 处理第一行/列。
  • 复杂度:两趟扫描,时间 (O(mn)),额外空间 (O(1))。
  • 最常见坑:忘记单独处理第一行/第一列,导致标记被覆盖后信息丢失。
  • 为什么要“先标记再清零”?:因为清零会改变原矩阵,如果一边扫一边清,会把新产生的 0 当成“原本就有的 0”,造成错误扩散。
类似题目(原地标记:用边界当标记位)
// 典型套路:把某一行/列/区域当做“标记数组”,再二次遍历应用标记

54. 螺旋矩阵

力扣原题:54. 螺旋矩阵
Medium
数组
矩阵
模拟

给你一个 mn 列的矩阵 matrix ,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。

 

示例 1:

输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[1,2,3,6,9,8,7,4,5]

示例 2:

输入:matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]
输出:[1,2,3,4,8,12,11,10,9,5,6,7]

 

提示:

  • m == matrix.length
  • n == matrix[i].length
  • 1 <= m, n <= 10
  • -100 <= matrix[i][j] <= 100
最优解(四个边界:上/下/左/右,按圈走)
正在加载编辑器...
最优解讲解(通俗版 + 推理过程)
  • 题意翻译:按螺旋方式输出矩阵:先最外圈一圈圈往里。
  • 关键思路:用“边界”描述当前还没走过的区域
    • 上边界 top、下边界 bottom、左边界 left、右边界 right
    • 每走完一条边,就把对应边界往里缩一格。
  • 为什么要做两次 if 判断?
    • 当矩阵变成一行或一列时,走完上边/右边后,可能已经没有下边或左边了;
    • top<=bottomleft<=right 是为了避免重复走/越界。
  • 直觉:你可以把它想成“剥洋葱”:每轮剥掉外层四条边,边界往里收缩,直到没有东西可剥。
  • 边界判断为什么是两次 if?:当只剩一行或一列时,底边/左边可能不存在,不加判断就会重复输出或越界。
  • 复杂度:每个格子输出一次,时间 (O(mn)),空间 (O(1))(不算结果数组)。
类似题目(边界收缩模板)
while (top <= bottom && left <= right) {
walkTop(); top++
walkRight(); right--
if (top <= bottom) { walkBottom(); bottom-- }
if (left <= right) { walkLeft(); left++ }
}

48. 旋转图像

力扣原题:48. 旋转图像
Medium
数组
数学
矩阵

给定一个 × n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。

你必须在 原地 旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。

 

示例 1:

输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[[7,4,1],[8,5,2],[9,6,3]]

示例 2:

输入:matrix = [[5,1,9,11],[2,4,8,10],[13,3,6,7],[15,14,12,16]]
输出:[[15,13,2,5],[14,3,4,1],[12,6,8,9],[16,7,10,11]]

 

提示:

  • n == matrix.length == matrix[i].length
  • 1 <= n <= 20
  • -1000 <= matrix[i][j] <= 1000

 

最优解(先转置,再每行翻转)
正在加载编辑器...
最优解讲解(通俗版 + 推理过程)
  • 题意翻译:把 (n\times n) 矩阵顺时针旋转 90°,原地完成。
  • 关键等价变形:顺时针 90° 的映射是:
    • 新位置 (i,j) 来自旧位置 (n-1-j, i)
  • 直接按映射挪会很麻烦:原地替换会覆盖尚未使用的值,需要分圈做四元交换,代码更绕。
  • 更好理解的推理:把旋转拆成两步
    1. 转置:把行列互换(沿主对角线翻折),(i,j) -> (j,i)
    2. 每行翻转:把转置后的每一行左右翻转 组合起来恰好等价于顺时针 90°。
  • 为什么这两步刚好等价?
    转置把 (n-1-j, i) 变成 (i, n-1-j)(落到第 i 行),再翻转一行把列 n-1-j 变成 j,于是得到 (i,j)
  • 复杂度:两次原地操作(转置 + 翻转),时间 (O(n^2)),额外空间 (O(1))。
类似题目(矩阵变换:用“拆解操作”代替复杂的原地搬运)
// 常见拆解:转置 + 翻转行/列;或按圈做四元交换

240. 搜索二维矩阵 II

力扣原题:240. 搜索二维矩阵 II
Medium
数组
二分查找
分治
矩阵

编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性:

  • 每行的元素从左到右升序排列。
  • 每列的元素从上到下升序排列。

 

示例 1:

输入:matrix = [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,13,14,17,24],[18,21,23,26,30]], target = 5
输出:true

示例 2:

输入:matrix = [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,13,14,17,24],[18,21,23,26,30]], target = 20
输出:false

 

提示:

  • m == matrix.length
  • n == matrix[i].length
  • 1 <= n, m <= 300
  • -109 <= matrix[i][j] <= 109
  • 每行的所有元素从左到右升序排列
  • 每列的所有元素从上到下升序排列
  • -109 <= target <= 109
最优解(从右上角出发:大了左移,小了下移)
正在加载编辑器...
最优解讲解(通俗版 + 推理过程)
  • 题目给了两个“单调性”:每行从左到右递增、每列从上到下递增。
  • 朴素做法:每行二分或全矩阵扫,能做但还可以更巧妙。
  • 关键选择:从右上角开始(也可以从左下角开始):
    • 右上角的特点:它的左边都更小,它的下边都更大。
  • 推理移动规则
    • 当前值 x
      • 如果 x > target:目标更小,只可能在左侧,所以 左移j--)。
      • 如果 x < target:目标更大,只可能在下方,所以 下移i++)。
    • 每一步都能排除一整行或一整列的候选区域,所以不会回头。
  • 复杂度:最多走 m+n 步,时间 (O(m+n)),空间 (O(1))。
类似题目(二维单调矩阵:从角出发“削掉一行/一列”)
let i = 0, j = n - 1
while (i < m && j >= 0) {
if (a[i][j] === target) return true
if (a[i][j] > target) j--
else i++
}
return false