ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

矩阵题通关手册:LeetCode热题100框架、Java实现与排坑指南

矩阵题通关手册:LeetCode热题100框架、Java实现与排坑指南 做Java面试准备的朋友对LeetCode hot100应该都不陌生这份刷题清单近两年几乎是后端岗位面试的标配。矩阵类型的题目在hot100里数量不算多但性价比极高——每道题都对应一个明确的面试考点而且非常考验对二维数组下标的掌控力。我印象很深有次面试官让我手写螺旋矩阵我因为少判断了一个边界条件在单行矩阵的用例上直接翻车。后来复盘才发现这类问题看似零散背后其实有一套统一的思维框架可以复用。这篇笔记就是把hot100中的矩阵类型题目做一次系统梳理从通用解题框架到高频题目的Java实现再到我自己刷题时踩过的坑一次性讲清楚。如果你正在准备Java岗位面试或者已经把hot100刷了一半但遇到矩阵题总是卡壳这篇笔记应该能帮你把这一块彻底拿稳。1. 矩阵题为什么是hot100的必拿分板块1.1 从面试角度看矩阵题矩阵在hot100中大概占6到8道题包括矩阵置零、螺旋矩阵、旋转图像、搜索二维矩阵这些经典题。单看数量好像占比不高但它在面试中的出现频率远超预期。原因很简单矩阵题能同时考察三个层面的能力——二维数组的遍历是否熟练、边界条件是否敏感、能否在空间复杂度上做出优化。很多人在一维数组题上很熟练但一到二维就乱了。本质还是没有把“矩阵”抽象成坐标系统来理解。矩阵里的每个元素都有两个维度的索引操作时经常需要同时关注行和列的变化这种思维方式和一维链表、数组完全不同。面试官很喜欢用矩阵题来快速判断候选人的代码基本功因为这类题套路化程度不算高但写起来很容易出错能真实反映平时的训练量。从另一个角度看矩阵题也是面试中的“送分题”。它有有限的题型、可总结的套路一旦掌握了正确的方法论短时间内能快速提高正确率。对比动态规划或者图论题需要较多的思维训练矩阵题更像是“熟练度游戏”——练过和没练过差距非常明显。1.2 矩阵题的通用解题框架我刷完hot100的矩阵题之后总结出一个三层框架几乎能覆盖所有矩阵类题目。第一层确定遍历路径。矩阵题本质是遍历问题关键是想清楚遍历顺序。常见的遍历方式有逐行遍历、逐列遍历、按层遍历螺旋、对角线遍历等。每道题的核心都是在确定一种合适的遍历顺序让问题在遍历过程中自然解决。比如旋转图像用“对角线左右翻转”两步遍历就是把复杂的旋转分解成两个简单的遍历步骤。第二层定位边界条件。二维数组的边界比一维复杂得多至少有上下左右四个方向的边界需要考虑。特判必须覆盖空矩阵、单行矩阵、单列矩阵、一个元素的矩阵。我建议每道题写完代码后第一时间用这四种特例结构去验证。很多线上提交不过的代码基本都是栽在这几个特殊形态上。第三层考虑空间优化。hot100里的矩阵题大部分都有原地算法或者O(1)额外空间的要求。核心思路是用矩阵自身来存储状态而不是额外开一个同样大小的标记数组。这个思维很关键因为它训练的是“在不改变语义的前提下复用已有资源”的工程能力面试中非常加分。这三层框架对应到具体题目上就是不同的实现组合。接下来先拆解几个通用的核心细节再上实操代码。2. 核心知识点与细节拆解2.1 矩阵遍历的四种基础模式任何一种矩阵题目最终都能落到四种基础遍历模式的组合上。把这四种模式练到条件反射刷题速度会明显提升。逐行遍历。最基础的模式双重循环中外层控制行、内层控制列。绝大多数暴力解都建立在这个模式上。需要注意的一点是把“行”和“列”谁放外层有时会影响算法的正确性比如后续要讲的矩阵置零标记阶段就必须按“行优先”处理才能避免错误覆盖。逐列遍历。外层控制列、内层控制行。个别题目需要这种视角比如搜索二维矩阵时从右上角出发的行列交替移动本质上就是列遍历和行遍历的组合应用。按层遍历螺旋。把矩阵看作层层嵌套的结构从外到内一圈一圈处理。每圈用四个for循环分别处理上、右、下、左四条边。难点在于内层循环的边界条件会随着圈的缩小而变化需要在每圈开始时重新计算top、bottom、left、right这四个变量。对角线遍历。分主对角线i j和副对角线i j n - 1两种。旋转图像第一步用到的就是主对角线翻转判断条件是 j i确保每个元素只交换一次。这四种遍历模式的好处在于只要熟练掌握遇到新题时可以通过“拆解为已知模式”来快速找到思路而不是每次都从零开始推导。2.2 原地算法的本质与状态标记技巧hot100矩阵题里的空间复杂度要求是区分“会做”和“做得漂亮”的分水岭。比如矩阵置零这道题最容易想到的解法是开两个布尔数组分别记录哪些行、哪些列需要置零。这样做的空间复杂度是O(mn)——看起来不算差但不满足最优解的要求。原地算法的核心思想是借用矩阵已有的空间来存储额外信息但前提是不干扰原始数据的读取。常见的技巧有两种用第一行和第一列做状态标记。既然要用O(1)空间那就把“哪一行需要置零”记录在第一列“哪一列需要置零”记录在第一行。但第一行和第一列本身也可能包含需要置零的元素所以必须先额外用两个布尔变量记录它们自身的原始状态。这种“牺牲两行一列存状态”的思路在整个hot100矩阵题中反复出现值得深入理解。用方向变化替代状态标记。螺旋矩阵和旋转图像这类题目则完全不同它们不需要存储状态而是通过控制遍历方向的变化来完成操作。这类题更要关注的是“什么时候改变方向”以及“改变方向后边界如何收缩”。一旦掌握写起来非常顺手。这两种思路分别对应了“空间换时间”和“方向控制”两大矩阵题类型把它们的区别搞清楚再看到新题时就能迅速判断该走哪条路。3. 高频题目实操从暴力解到最优解3.1 矩阵置零LeetCode 73题目给定一个 m x n 的矩阵如果某个元素为 0则将其所在行和列的所有元素都设为 0。要求使用原地算法。这道题是hot100矩阵题的“开胃菜”也是一道非常经典的原地算法训练题。思路演进最简单的方法是复制一份矩阵然后遍历原矩阵找出所有0的位置再在副本上置零。空间复杂度O(m*n)面试这么写基本没戏。进一步优化是用两个数组分别记录需要置零的行和列空间复杂度O(mn)但仍然不是最优。最优解就是利用矩阵的第一行和第一列作为标记位把空间压到O(1)。Java实现public void setZeroes(int[][] matrix) { int m matrix.length; int n matrix[0].length; boolean firstRowZero false; boolean firstColZero false; // 1. 检查第一行和第一列本身是否有0 for (int j 0; j n; j) { if (matrix[0][j] 0) { firstRowZero true; break; } } for (int i 0; i m; i) { if (matrix[i][0] 0) { firstColZero true; break; } } // 2. 从第二行第二列开始找0标记在第一行和第一列 for (int i 1; i m; i) { for (int j 1; j n; j) { if (matrix[i][j] 0) { matrix[i][0] 0; matrix[0][j] 0; } } } // 3. 根据标记将非第一行第一列的位置置零 for (int i 1; i m; i) { for (int j 1; j n; j) { if (matrix[i][0] 0 || matrix[0][j] 0) { matrix[i][j] 0; } } } // 4. 最后处理第一行和第一列 if (firstRowZero) { for (int j 0; j n; j) { matrix[0][j] 0; } } if (firstColZero) { for (int i 0; i m; i) { matrix[i][0] 0; } } }关键点解析步骤2和步骤3的遍历范围都从第二行第二列开始这是必须的——第一行和第一列现在承担的是标记职责如果把它们也纳入标记扫描的范围标记信息会被后续的置零操作覆盖掉。顺序上也要特别注意必须先用标记位完成主体区域的置零最后再处理第一行和第一列本身。如果先处理第一行它的0值信息就丢了后面的判断就会出错。复杂度时间复杂度O(m*n)空间复杂度O(1)。矩阵置零的空间优化思路几乎可以原封不动地迁移到其他“标记类”题目中这是我刷题时觉得收益最大的地方之一。3.2 螺旋矩阵LeetCode 54题目给你一个 m 行 n 列的矩阵 matrix请按照顺时针螺旋顺序返回矩阵中的所有元素。这道题是hot100矩阵题中代码量最大但也最考验边界控制的一道。思路演进暴力方法是模拟路径用一个visited数组记录哪些位置已经访问过遇到边界或已访问位置就转向。这么做简单直观但需要O(m*n)的额外空间。更优雅的做法是用四个指针top、bottom、left、right维护当前未遍历的边界每遍历完一条边就收缩对应指针不需要额外的visited数组。Java实现public ListInteger spiralOrder(int[][] matrix) { ListInteger result new ArrayList(); if (matrix null || matrix.length 0 || matrix[0].length 0) { return result; } int top 0; int bottom matrix.length - 1; int left 0; int right matrix[0].length - 1; while (top bottom left right) { // 上部从左到右 for (int j left; j right; j) { result.add(matrix[top][j]); } top; // 右部从上到下 for (int i top; i bottom; i) { result.add(matrix[i][right]); } right--; // 下部从右到左需要判断是否仍然有效 if (top bottom) { for (int j right; j left; j--) { result.add(matrix[bottom][j]); } bottom--; } // 左部从下到上需要判断是否仍然有效 if (left right) { for (int i bottom; i top; i--) { result.add(matrix[i][left]); } left; } } return result; }关键点解析后两个for循环一定要加if (top bottom)和if (left right)的保护。不加的话当矩阵是单行或单列形态时会出现重复遍历甚至数组越界。很多人第一次写螺旋矩阵都会在这里翻车。我当时排查了整整二十分钟最后发现是单列矩阵时底部遍历和顶部遍历重叠了。单行矩阵的推演假设matrix是[[1, 2, 3]]初始top0、bottom0、left0、right2。第一步上部遍历完成后top变成1。此时top1 bottom0循环条件不满足直接退出。由于第一步已经把所有元素都加入了result结果是正确的。如果没有第三第四两个if保护在top变成1之后还会继续执行下部遍历再次添加元素导致结果错误。复杂度时间复杂度O(m*n)空间复杂度O(1)不包含返回列表占用的空间。螺旋矩阵的边界收缩逻辑在旋转图像这类题目中也会以不同的形式出现掌握这一道等于掌握了一类。3.3 旋转图像LeetCode 48题目给定一个 n × n 的二维矩阵 matrix 表示一个图像请你将图像顺时针旋转 90 度。要求原地旋转。思路演进直接原地旋转每个元素需要维护四个位置的轮换很容易写乱。更清晰的思路是利用线性代数的结论顺时针旋转90度等于先沿主对角线翻转再沿垂直中线左右翻转。这个过程很符合直觉——对角线翻转相当于转置左右翻转相当于水平镜像两者叠加就是旋转。Java实现public void rotate(int[][] matrix) { int n matrix.length; // 第一步沿主对角线翻转转置 for (int i 0; i n; i) { for (int j i 1; j n; j) { int temp matrix[i][j]; matrix[i][j] matrix[j][i]; matrix[j][i] temp; } } // 第二步沿垂直中线左右翻转 for (int i 0; i n; i) { for (int j 0; j n / 2; j) { int temp matrix[i][j]; matrix[i][j] matrix[i][n - 1 - j]; matrix[i][n - 1 - j] temp; } } }关键点解析对角线翻转的内层循环从j i1开始这是为了避免重复交换。假如从j0开始那么(i0,j1)和(i1,j0)这两对会分别执行一次交换等于把已经交换过的元素又换回去了。理解这个“只处理上三角”的逻辑是写对转置的关键。逆时针旋转90度怎么做同样拆成两步先沿副对角线翻转条件是i j n - 1再左右翻转。或者先做转置再上下翻转。刷题时不妨把两种旋转都练熟很多面试题会从顺时针出发做变形考察。复杂度时间复杂度O(n²)空间复杂度O(1)。这个解法的精妙之处在于完全不需要额外空间而是通过两次“对称操作”的组合完成了旋转非常体现矩阵思维。3.4 搜索二维矩阵 IILeetCode 240题目编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target。该矩阵具有以下特性每行的元素从左到右升序排列每列的元素从上到下升序排列。思路演进最直接的暴力双重循环是O(mn)面试中不会满意。由于行列都有序可以利用“二分查找”在每一行搜索复杂度降到O(mlog n)。但最优解利用了矩阵的特殊特性从右上角出发每次比较后可以排除一行或一列把复杂度降到O(mn)。为什么选右上角因为右上角的元素有一个非常好的性质它是所在行的最大值、所在列的最小值。如果target比它大说明target不可能在这一行row向下移一行如果target比它小说明target不可能在这一列col--向左移一列。每走一步就排除整整一行或一整列。Java实现public boolean searchMatrix(int[][] matrix, int target) { if (matrix null || matrix.length 0 || matrix[0].length 0) { return false; } int row 0; int col matrix[0].length - 1; while (row matrix.length col 0) { int value matrix[row][col]; if (value target) { return true; } else if (value target) { col--; } else { row; } } return false; }关键点解析从左上角出发是错误的选择。左上角的元素是所在行和列的最小值如果target比它大无法确定是排除这一行还是这一列因为有可能在行方向也可能在列方向。右上角和左下角具备“单向排除”的特性所以才是正解。同理从左下角出发也是可行的判断逻辑正好反过来。复杂度时间复杂度O(mn)空间复杂度O(1)。这是hot100矩阵题中最能体现“观察性质优于直接计算”的一道也是面试官比较容易追加追问的题目——比如改成第一行升序但列不严格升序时还能不能用这个思路。4. 实战踩坑与排查技巧实录4.1 边界条件引发的经典Bug刷完这批矩阵题我发现80%的提交失败都出在边界条件上。分享几个我在实际编码中反复遇到的高频Bug。数组越界问题。螺旋矩阵中如果矩阵只有一行第一次上部遍历后top变成1此时如果下部循环没有保护判断会在访问matrix[bottom][...]时仍然使用bottom0出现重复添加。更严重的情况是当矩阵只有一列时右部遍历和左部遍历可能互相重叠。这类问题只能靠特殊用例验证来排除写完代码后我习惯先跑一下四组特例[[1]]、[[1,2,3]]、[[1],[2],[3]]、[[1,2],[3,4]]基本能覆盖90%的隐患。数组下标搞混。在矩阵置零里内部循环经常会出现把matrix[i][0]写成matrix[0][i]的情况。二维数组的第一个索引是行、第二个是列但写代码时因为注意力集中在循环变量上很容易手滑。我的经验是在涉及“固定某一行或某一列”的操作时先把索引含义写在注释里比如// matrix[i][0] 表示第i行的第一列能有效减少这类低级错误。变量命名引发的逻辑错乱。如果统一用i和j表示行、列在多道题中会造成混淆。我在旋转图像中改用了row和col命名后代码的可读性和正确率都显著提升。好的命名不仅方便他人阅读更重要的是帮助自己维持清晰的空间认知。4.2 常见问题速查表题目最常见的错误排查心得矩阵置零先处理第一行/列导致标记丢失严格按“扫描标记→主体置零→处理首行首列”三步走螺旋矩阵单行单列时重复遍历后两个for循环必须加top/bottom或left/right的合法性判断旋转图像转置时重复交换元素内层循环从ji1开始只处理上三角搜索二维矩阵从左上角出发导致方向判断失效必须从右上角或左下角出发利用单向排除除了题目本身的错误我还遇到过几个比较隐蔽的坑。比如在使用matrix.length获取列数时有人会下意识地认为所有行长度相同但题目如果允许不规则矩阵就必须在每一行单独获取长度。好在hot100的矩阵题都保证是合法矩形但做工程时一定要警惕。另一个坑是函数签名中用了int[][] matrix在Java中引用类型传参时修改会影响原数组这一点其实是利用它实现原地算法的基础。可面试时如果把原地算法写成了返回新数组虽然功能正确但不满足题目要求也会被扣分。4.3 刷题顺序与时间分配建议hot100矩阵题建议按照由易到难的顺序推进先做搜索二维矩阵 II再旋转图像然后是矩阵置零最后啃螺旋矩阵。前两题练观察力后两题练边界控制。这个顺序能让难度曲线比较平滑不会第一题就把信心打没。每道题的训练目标也要明确搜索二维矩阵 II重点在于理解“为什么右上角出发是可行的”旋转图像重点在于拆解操作的组合逻辑矩阵置零重点在于原地状态标记的完整流程螺旋矩阵重点在于四指针的同步收缩。这样带着问题去刷效率比盲目重复提交高得多。时间分配上我建议每道题先独立思考15分钟有思路就动手写没思路就直接看主流解法然后合上答案自己默写一遍。默写时特别注意我在上文中提到的边界逻辑能写出和标准解结构一致且能跑通特殊用例的代码才算真正掌握。最后聊点实操体会这批矩阵题刷完之后我最明显的变化是看到二维数组不会心慌了。以前总觉得自己在矩阵题上“差点意思”但找不到具体的短板。现在回头看其实就是没有把“遍历路径、边界控制、空间优化”这三件事拆开来看。每道题的本质都能归到其中一个或几个组合一旦建立了这种归类思维新题也不再陌生。最后再分享一个小技巧平时刷题时在编辑器里把四类特殊用例提前存成代码片段每次写完矩阵相关的题就顺手验证一遍。我靠着这个习惯后来在面试中手写螺旋矩阵时一遍通过几乎没有停顿。矩阵题是hot100里最讲究“熟练度”的板块练到位之后它们反而会成为你面试中最稳的送分题。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进