LeetCode 867 这道题题面短到几乎没有存在感给你一个二维矩阵返回它的转置。按照网上常见的题解写法核心代码只有三行外层循环行内层循环列把matrix[i][j]放进res[j][i]就完事。但我在第一次刷这道题的时候实际花掉的时间是 100 分钟。不是环境出了问题也不是题目读不懂而是围绕「转置到底改变了什么」这件事我在脑子里反复推演了很久。真正卡住我的不是写循环而是确认自己有没有把行列关系彻底想清楚。这篇文章不是来教你背答案而是想还原一下一道简单题在资深思路里到底应该怎么拆解。适合刚开始刷题、对二维数组下标容易晕的人也适合想从一道题里延伸出矩阵操作基本功的读者。1. 为什么一道简单题会花掉 100 分钟拆一下时间账1.1 先看懂题目在问什么第 867 题「Transpose Matrix」中文一般叫「转置矩阵」。它做的事情很朴素把原矩阵的行变成新矩阵的列把原矩阵的列变成新矩阵的行。比如一个 2 行 3 列的矩阵[[1, 2, 3], [4, 5, 6]]转置以后得到 3 行 2 列[[1, 4], [2, 5], [3, 6]]看起来很简单对吧转置的定义一句话就能说清楚但你真去写代码至少要处理两个问题新矩阵的尺寸是多少下标从旧数组到新数组怎么映射这也是我 100 分钟里真正反复确认的核心。1.2 我最初浪费时间的三个地方第一个坑是把行数和列数写反。我当时下意识以为「转置就是行列数互换所以两个维度调一下就行」于是写下了类似new int[rows][cols]的初始化结果一运行就数组越界。仔细一想才反应过来原来的rows是 2cols是 3转置后应该是 3x2新数组的初始化必须写成new int[cols][rows]。第二个坑是纠结「要不要在原地交换元素」。因为我之前刚刷过一些原地操作数组的题遇到「变换矩阵」就下意识想省空间。可这题要求的是返回一个新数组不是修改原数组你非要在原数组上折腾反而把自己绕晕。后来我翻题目给出的函数签名确认了一下返回类型是新的二维数组原地操作在这个题里根本没有必要成为优先考虑的方向。第三个坑是怀疑「转置是不是等于把二维数组 reverse 一下」。我盯着输出看了很久总觉得[[1,4],[2,5],[3,6]]和原数组之间应该有某种对称关系但一下子又说不上来这种对称到底怎么用下标表达。现在回头看这就是典型的「生活直觉」和「下标语言」没有互相翻译过来。1.3 这份时间账花得值不值如果只看提交时间100 分钟换一道简单题效率确实不高。但从理解牢固程度来说这份时间账很值。因为在那之后我遇到任何矩阵下标变换的题都习惯先在纸上画出新旧形状再写循环基本没再因为行列反了而翻车。所以我想说简单题花时间不丢人真正亏的是「AC 了但不知道自己在做什么」。很多人刷题喜欢追求最短通过时间但第 867 题这种题目恰恰是花时间建立下标直觉的好机会过了这村就没这店。2. 转置矩阵的本质索引互换不是把数组倒过来2.1 一个座位表的类比为了让自己彻底想明白我当时把矩阵想象成一张班级座位表。原矩阵里行是学生列是科目每个格子里是某个学生在某科的成绩。转置以后行变成科目列变成学生格子里装的内容不变变的只是这张表从哪里看、怎么排。这个类比看起来很幼稚但特别有用。它让我抓住一个关键转置改变的不是元素值而是元素的位置索引。matrix[i][j]这个元素无论放在哪它还是原来的值只是它在新的坐标系里被重新安排了座位。理解这一点以后写代码时就再也不会想着「把数组里的元素倒序」之类的事了。2.2 核心公式result[j][i] matrix[i][j]转置的数学表达非常简单设原矩阵为 A转置后的矩阵为 B那么B[j][i] A[i][j]。也就是说原矩阵的行号变成新矩阵的列号原矩阵的列号变成新矩阵的行号。用代码语言翻译一下rows len(matrix) cols len(matrix[0]) res [[0] * rows for _ in range(cols)] for i in range(rows): for j in range(cols): res[j][i] matrix[i][j]这里最关键的一点是循环遍历的是原矩阵的每一个位置(i, j)然后把它的值放到新矩阵的(j, i)去。如果你用的是res[i][j] matrix[j][i]这种写法逻辑上也成立但你需要保证i遍历的是新矩阵的行号范围j遍历的是新矩阵的列号范围也就是i要从 0 到cols-1j要从 0 到rows-1。两种写法的本质一模一样只是「谁当外层循环」的区别。2.3 为什么直觉会反下标语义要先定死我后来发现自己浪费那么多时间很大程度是因为在两种写法之间反复横跳。一会儿想着「从原矩阵找新位置」一会儿又想着「从新矩阵找旧位置」一下就把自己绕进去了。这里分享一个我的经验一次性只选一种视角别来回切换。如果你选择「遍历原矩阵把元素送到新矩阵」那么所有循环变量都基于原矩阵的行列范围赋值语句只有res[j][i] matrix[i][j]这一种。如果你选择「遍历新矩阵从原矩阵取元素」那么循环变量基于新矩阵的形状赋值语句只有res[i][j] matrix[j][i]这一种。选定以后就不要中途换视角这是避免低级错误最有效的方法。3. 实现方案与复杂度返回新矩阵是最稳妥的解法3.1 三种常见语言的写法Python 版本class Solution: def transpose(self, matrix: List[List[int]]) - List[List[int]]: rows len(matrix) cols len(matrix[0]) res [[0] * rows for _ in range(cols)] for i in range(rows): for j in range(cols): res[j][i] matrix[i][j] return resJava 版本class Solution { public int[][] transpose(int[][] matrix) { int rows matrix.length; int cols matrix[0].length; int[][] res new int[cols][rows]; for (int i 0; i rows; i) { for (int j 0; j cols; j) { res[j][i] matrix[i][j]; } } return res; } }C 版本class Solution { public: vectorvectorint transpose(vectorvectorint matrix) { int rows matrix.size(); int cols matrix.empty() ? 0 : matrix[0].size(); vectorvectorint res(cols, vectorint(rows, 0)); for (int i 0; i rows; i) { for (int j 0; j cols; j) { res[j][i] matrix[i][j]; } } return res; } };三种写法结构一致都是先取行列数再初始化新矩阵最后二重循环赋值。语言本身的差异不会影响核心逻辑你能完整写出任何一种说明思路已经通了。3.2 初始化维度为什么是最容易出错的地方我见过很多人第一次写这道题包括我自己最容易在res的初始化上报错。原因很简单我们把「转置」理解成「形状互换」于是顺手把rows和cols交换了一下写成了new int[rows][cols]。但如果你认真推一遍会发现rows表示原矩阵的行数cols表示原矩阵的列数转置后的矩阵应该是「原列数行、原行数列」也就是cols行、rows列。所以在 Java/C/Python 里都要写成res[cols][rows]这种形式。这不是什么高深知识点纯粹是「先把形状画出来」就能避免的坑。提示写这类题时先别碰代码在纸上写一行原矩阵 2x3转置矩阵 3x2。把目标形状确定下来再去初始化数组基本就不会犯这种低级错误。3.3 方阵原地转置能写但不是首选如果原矩阵是方阵也就是rows cols那么转置前后形状不变确实可以做原地转置。方法也很经典沿主对角线交换元素。for (int i 0; i rows; i) { for (int j i 1; j cols; j) { swap(matrix[i][j], matrix[j][i]); } }这里有一个非常重要的细节内层循环从i 1开始不能从 0 开始。如果你从 0 开始等于把矩阵里每一对(i, j)和(j, i)交换了两次最后等于没换。这个错误在我最初踩坑的时候也遇到过而且非常隐蔽因为对于 2x2 的小矩阵你可能恰好看不出来。但第 867 题并不要求原地而且输入输出形状在非方阵时根本不同所以用新数组返回是最安全、最符合题意的做法。方阵原地转置可以作为知识扩展去练习但不要把它当成这道题的标准答案。3.4 复杂度分析时间复杂度上二重循环遍历了原矩阵的全部元素矩阵大小是rows * cols所以时间是 O(nm)。空间复杂度上新建了一个同样规模的矩阵所以额外空间是 O(nm)。如果改成方阵原地转置额外空间可以降到 O(1)但代价是只适用于方阵并且修改了原矩阵。面试时如果被追问「能不能优化空间」你可以直接给出这两种方案的对比这比闷头写代码要加分。3.5 转置不是旋转别混淆这道题很常见的一个混淆点是「转置」和「旋转 90 度」之间的关系。转置只做行列互换不做行的倒序或列的倒序。而顺时针旋转 90 度需要两步先把行变列再把每一行反转。它们是两种不同的矩阵操作。如果你把第 867 题和另一道「旋转图像」的题放在一起做很快就会发现两者的下标映射公式完全不同。提前区分清楚省得后面刷题时串味。4. 边界条件与测试用例这几组用例不写AC 了也心虚4.1 空矩阵与空行第 867 题的输入理论上是一个二维矩阵但你不能假设它一定非空。如果matrix []此时matrix[0]根本不存在直接取matrix[0].length会越界。C 里我写了matrix.empty()的判断Python/Java 里也可以先判断matrix null || matrix.length 0或者matrix []。还有一种边界是matrix [[]]它表示一个没有元素的行。此时rows 1cols 0转置后的矩阵应该是 0 行 1 列还是 1 行 0 列表严格来说转置结果应该是一个空矩阵。如果你不做特殊处理初始化res的时候用[0] * rows可能得到[[0]]这就不对了。所以要么提前判空返回空数组要么在初始化逻辑里处理cols 0的情况。实际提交时不一定测到这种输入但本地验证的时候一定要自己加上。4.2 单行、单列、1x1单行矩阵[[1, 2, 3]]是 1x3转置后变成 3x1[[1], [2], [3]]单列矩阵[[1], [2], [3]]是 3x1转置后变成 1x3[[1, 2, 3]]这两组用例特别适合检验你有没有把rows和cols搞反。很多人写普通 2x3 矩阵时能蒙对一旦遇到 1x3 或 3x1行列就乱了。1x1 矩阵[[1]]转置后还是[[1]]这是最简单的用例可以用来验证代码没有额外报错。4.3 一个本地验证转置是否正确的通用方法转置有一个非常好的性质对任意矩阵 A转置两次会得到原来的矩阵transpose(transpose(A)) A。这意味着你可以写一个小脚本随机生成各种形状的矩阵然后连续调用自己的transpose函数两次最后比对结果。import random def transpose(matrix): rows len(matrix) cols len(matrix[0]) if rows 0 else 0 if rows 0 or cols 0: return [] res [[0] * rows for _ in range(cols)] for i in range(rows): for j in range(cols): res[j][i] matrix[i][j] return res for _ in range(1000): rows random.randint(0, 5) cols random.randint(0, 5) matrix [[random.randint(-10, 10) for _ in range(cols)] for _ in range(rows)] if rows 0 or cols 0: continue assert transpose(transpose(matrix)) matrix这个测试能跑通说明你的实现大概率是正确的。而且它比手动看输出要省心得多。我在刷题时会保留这种本地验证脚本遇到矩阵类题目先跑一遍再提交平台能省掉很多试错时间。5. 从第 867 题往前看矩阵操作题的基本功5.1 面试里可能的追问方向第 867 题本身简单但面试官如果想深挖完全可以从这道题延伸出很多问题。第一个追问是「如果矩阵是方阵能不能原地转置」这个上面已经讲过关键在于主对角线交换并且内层循环从i 1开始。第二个追问是「如果矩阵特别大内存放不下该怎么转置」。这种场景在真实工程里很常见比如一张超大位图、一个大规模稀疏矩阵。答案通常涉及分块处理把大矩阵分成若干小块每次只读两块进内存交换对应位置的元素。这个方向已经超出简单算法题本身但它确确实实是转置在生产环境中的一个真实应用场景。第三个追问可能是「转置在图像处理里有什么用」。灰度图像在程序里就是一个二维数组转置之后相当于把图像沿着主对角线镜像了一下再加上行反转或列反转就能实现各种旋转效果。如果你能把第 867 题和图像处理联系起来面试官对你的印象会比只会背题解的人好很多。5.2 一张索引映射表我在刷完这道题后做了一张小表格把常见的矩阵变换全部列在一起方便记忆和理解。这张表对我的帮助非常大每次遇到矩阵操作题我都会先在脑子里过一遍这张表。变换类型原矩阵 (i, j) 映射到新矩阵的位置转置(j, i)顺时针旋转 90 度(j, rows-1-i)逆时针旋转 90 度(cols-1-j, i)水平翻转(i, cols-1-j)垂直翻转(rows-1-i, j)这张表的价值在于它把所有常见矩阵变换统一成了「下标映射」这一个视角。第 867 题只是其中最简单的一种映射。如果你能自己推导出这张表里的每一个公式那么矩阵相关的题对你来说就不再是「背模板」而是「查字典」。5.3 一道题延伸出的一类题刷完转置矩阵后你会遇到很多姿势相似但要求不同的矩阵题。比如旋转图像核心是四次旋转交换螺旋矩阵核心是控制边界收缩对角线遍历核心是行下标加列下标的奇偶判断矩阵置零核心是标记空间如何压缩。这些题的共同点都是「二维数组下标变换」差别在变换规则和边界条件。所以我的建议是不要只满足于第 867 题通过。你可以试着在本地把这张表里的每一种变换都写一遍哪怕不提交到平台也能显著提升下标的直觉。这种练习比盲目刷三五十道简单题更有效。6. 我的实操建议这样刷简单题不亏6.1 把 AC 当作开始而不是终点第 867 题 AC 之后我做的第一件事不是赶着刷下一题而是把代码本地运行了一遍把输入输出都打印出来看。第一次打印 2x3 矩阵的转置结果时我盯着那个 3x2 的矩阵看了很久终于体会到「行列互换」不是一句背出来的定义而是肉眼可见的形状变化。然后我又写了一个「遍历新矩阵、从旧矩阵取值」的版本和标准版对比发现两个版本的循环方向互为镜像。这种自己动手验证的过程比看十遍题解都有效。6.2 用「转置两次回到原点」验证直觉前面提到过转置两次会回到原矩阵。这个性质我每次写完转置代码都会拿来当自检工具。它最大的好处是不依赖参考答案。你不知道自己的实现对不对的时候就随机生成几个矩阵转置两次再和原数组比较。如果一致基本可以确定核心逻辑没问题如果不一致说明下标映射或者初始化维度出了问题。这个方法简单粗暴但非常好使。6.3 别被耗时记录绑架回到标题里的「耗时 100」。刷题平台上的耗时记录很容易让人焦虑尤其是看到别人几秒钟就完成了同一道题。但我的真实感受是第 867 题花掉的那 100 分钟远比我后来花 5 分钟刷过的那些所谓中等题有价值。因为简单题一旦想通你会获得一个非常牢固的基础直觉而中等题如果只是对着题解滑过去三天后就忘了。耗时记录只是你理解过程的痕迹不是效率低的判决书。如果你现在也卡在某道简单题上两小时别慌这很正常。6.4 最后分享一个刷题时的小习惯我在刷矩阵类题目时有个习惯先在纸上或者注释里写清楚输入和输出的形状。比如写一行2x3 - 3x2再写一行映射公式res[j][i] matrix[i][j]然后才开始写代码。这个习惯看起来浪费时间实际上能帮你把思路固定在正确的坐标系里避免数组越界也避免行列搞反。第 867 题之后我把这个习惯沿用到了所有二维数组题上至今仍觉得非常值。第 867 题「Transpose Matrix」是一道很典型的入门级二维数组操作题它不会让你感到高深但它考验的是你是否真的理解了下标映射这件事。100 分钟换来的不是一段 AC 代码而是一种一看矩阵就条件反射般在脑中画出形状变化的直觉。如果你现在也卡在自己的「耗时 100」上不妨把节奏放慢一点先别急着看题解试着把那个为什么彻底想明白。后面再遇到类似题目时你会感谢今天这个肯花时间的自己。