资讯动态

LeetCode 48 图像旋转:矩阵90度旋转三种解法对比

发布时间:2026/9/18 13:21:25 来源:尧图企业网站定制
看到 LeetCode 48 图像旋转这道题很多人的第一反应是这有什么难的把每个元素按位置搬到新数组不就完事了。但等到面试官补一句“能不能不用额外空间”或者要求你当场写出 bug-free 的原地旋转版本时才发现里面全是细节。题目本身不复杂输入一个 n x n 的二维矩阵把它顺时针旋转 90 度。示例中三阶矩阵转一圈行变成列、列倒过来视觉上很清楚难的是如何在不申请大额外空间的前提下用下标操作完成整个变换。这篇文章我用 Python 和 Java 双语言把三种主流解法完整拆开辅助数组法、原地四元素轮转法、转置加翻转法。三种思路从易到难从直观到精巧正好对应面试中从“能做出”到“能做漂亮”的进阶过程。适合刚开始刷题、被二维数组绕晕的读者也适合准备面试时想把自己的答案讲出层次感的朋友。1. 旋转的本质先搞清楚下标映射1.1 题目在考什么LeetCode 48 的题目描述很简短给定一个 n × n 的二维矩阵表示一张图像将图像顺时针旋转 90 度。要求原地旋转也就是说不能新建一个矩阵来接收结果必须在原矩阵上直接改动。你可能会想图像旋转不是常规操作吗真正实现起来每一位元素要移动到哪一格都对应一个严格的下标关系。本题考的并不是高深的算法而是几个点映射关系是否清晰、循环边界是否严谨、能否从 O(n^2) 空间优化到 O(1) 空间。这也是为什么它在热门 100 题里长期占位的根本原因——虽然看起来只是 Medium 难度但写好并不容易。很多人在刷题群里抱怨“看题解三分钟就懂过两天自己写又错”基本都栽在边界条件上。1.2 用四角推导映射关系以 3x3 矩阵来走一遍原矩阵是 [[1,2,3],[4,5,6],[7,8,9]]旋转后是 [[7,4,1],[8,5,2],[9,6,3]]。我们观察四个角的迁移路径1 从 (0,0) 到 (0,2)3 从 (0,2) 到 (2,2)9 从 (2,2) 到 (2,0)7 从 (2,0) 到 (0,0)用一个式子概括就是原矩阵中的 matrix[i][j]旋转后应该去 matrix[j][n-1-i] 这个位置。例如 (0,0) 代入得到 (0,2)正确 (0,2) 代入得到 (2,2)正确。反过来新矩阵中 (i,j) 位置的值来自原矩阵的 (n-1-j,i)这个反推公式也非常常用特别是写辅助数组版本时按新矩阵的扫描顺序去旧矩阵里取值往往比正向填值更顺手。1.3 映射关系是所有解法的锚点很多人刷题时喜欢直接背解法三的代码背下来之后能过但换个方向比如逆时针旋转就懵了。原因是没抓住映射关系。其实不管解法怎么变核心都是同一个公式。辅助数组版本是公式的直接翻译四元素轮转是公式在固定范围上做了四次复合转置加翻转则是把公式拆成两步线性变换。理解到这一层就能自己推导出逆时针 90 度映射是 (i,j)-(n-1-j,i)、180 度之类的变体而不是每次遇到新题都从头开始猜代码。2. 解法一辅助数组——先用最直观的方式保证正确2.1 思路辅助数组法的思路直白得不能再直白开一张同样大小的二维数组 rotated遍历原矩阵的每个格子根据映射关系把值填到 rotated 对应位置。最后再把 rotated 复制回 matrix。由于我们是按“目标位置接收来源值”的方式写直接用新表扫描更简单rotated[i][j] matrix[n-1-j][i]。这句话的意思是新矩阵的第 i 行第 j 列应该取原矩阵的第 n-1-j 行、第 i 列的元素。2.2 Python 实现from typing import List def rotate(self, matrix: List[List[int]]) - None: n len(matrix) rotated [[0] * n for _ in range(n)] for i in range(n): for j in range(n): rotated[i][j] matrix[n - 1 - j][i] for i in range(n): for j in range(n): matrix[i][j] rotated[i][j]注意最后一步必须做两层复制。如果直接写matrix rotated外面的调用方根本感知不到变化。在 LeetCode 这类 OJ 上它检查的是 matrix 这个对象的内容你改掉局部变量引用是无效的。这也是 Python 里非常经典的一个坑。如果你想要写法更简洁最后一层复制也可以写成matrix[:] rotated列表的切片赋值会原地修改外层列表的内容。但两层循环更通用尤其在面试手写场景下不容易让面试官产生疑惑。2.3 Java 实现class Solution { public void rotate(int[][] matrix) { int n matrix.length; int[][] rotated new int[n][n]; for (int i 0; i n; i) { for (int j 0; j n; j) { rotated[i][j] matrix[n - 1 - j][i]; } } for (int i 0; i n; i) { matrix[i] rotated[i].clone(); } } }这里的 clone() 或者 System.arraycopy 都可以目的都是把一维数组的内容真正拷贝进原来的行数组。Java 二维数组本质上是数组的数组直接matrix[i] rotated[i]其实也可以因为是把整行引用替换掉原二维数组的外层容器还在。但我个人建议用 clone() 或 arraycopy语义更清晰面试时也能展示你对引用的理解。2.4 复杂度与定位时间 O(n^2) 是不可避免的因为 n^2 个元素都要挪位置。额外空间 O(n^2)。这个版本应该作为思路铺垫而不是最终答案。为何还要写它因为面试时先讲一个正确但不够优的版本再讲优化能直观展示“识别问题 - 优化问题”的思维方式。而且辅助数组版本最容易验证映射公式没有写反很多人用解法三出 bug 时都会回到这个版本做对照。3. 解法二原地四元素轮转——空间压缩到 O(1)3.1 为什么要一圈一圈处理在原地旋转的前提下我们不能把某个元素直接搬走到另一个位置后不管它因为那样会覆盖还没处理的值。解决思路是“分组交换”四个在旋转路径上互相接力的元素刚好构成一个环把它们作为一个小组同时旋转。整个矩阵可以看成一层套一层的方框外圈旋转后仍在最外圈内圈旋转后仍在更内圈。因此从最外层开始逐层向里处理即可。最内层如果只有一个中心元素n 为奇数它旋转后仍在自己位置不需要处理。3.2 四元素组的坐标公式对于第 layer 圈定义 first layerlast n - 1 - layer。这一圈的边长是 last - first 1除了四个角以外每条边上还有若干中间元素。取一个偏移量 offset范围是 0 到 last - first - 1左闭右开四个对应位置分别是上边matrix[first][first offset]右边matrix[first offset][last]下边matrix[last][last - offset]左边matrix[last - offset][first]这四者正好构成一个旋转闭环上边跑到右边右边跑到下边下边跑到左边左边跑回上边。算法上为了不丢失值先把上边存到临时变量 top然后执行“左 - 上下 - 左右 - 下上 - 右”的逆方向赋值最后把 top 放到右边的位置。3.3 完整代码Pythonfrom typing import List def rotate(self, matrix: List[List[int]]) - None: n len(matrix) for layer in range(n // 2): first layer last n - 1 - layer for i in range(first, last): offset i - first top matrix[first][first offset] matrix[first][first offset] matrix[last - offset][first] matrix[last - offset][first] matrix[last][last - offset] matrix[last][last - offset] matrix[first offset][last] matrix[first offset][last] topJavaclass Solution { public void rotate(int[][] matrix) { int n matrix.length; for (int layer 0; layer n / 2; layer) { int first layer; int last n - 1 - layer; for (int i first; i last; i) { int offset i - first; int top matrix[first][first offset]; matrix[first][first offset] matrix[last - offset][first]; matrix[last - offset][first] matrix[last][last - offset]; matrix[last][last - offset] matrix[first offset][last]; matrix[first offset][last] top; } } } }3.4 边界陷阱offset 为什么要排除最后一个offset 的最大值是 last - first - 1而不是 last - first。因为 offset 如果取到 last - first那么“上边”位置 first offset 就等于 last此时四个位置全部落在四个角上而这四个角已经在上一个 offset 中被处理过了。再处理一遍相当于多旋转一次会把刚才已经放好的四角又换回去。这是原地旋转最容易犯的 off-by-one 错误。建议写完后用 4x4 矩阵手推一遍外圈 offset 只取 0、1、2三个元素组刚好覆盖外圈 12 个位置。3.5 坐标方向怎么记才不会乱这版代码最容易卡壳的是四行赋值的方向。我的记忆技巧是永远写“拿后来者填补当前位置”的逆推版本。也就是说要让上边位置变成左边位置的值要让左边位置变成下边位置的值要让下边位置变成右边位置的值要让右边位置变成原来的上边值。这样四个赋值语句的顺序是固定的配合 top 临时变量不会出现覆盖原始值的问题。很多资料给的是“上给右、右给下、下给左、左给上”的正向版本思路也行但方向反了容易混淆选一种记住就好。我自己在面过几个候选人后发现凡是能把这四行赋值顺序讲清楚的人对二维数组下标的掌控感明显更强。4. 解法三转置加翻转——最优雅的写法4.1 转置先沿主对角线转置把 matrix[i][j] 和 matrix[j][i] 交换。注意只遍历 i1 到 n-1也就是矩阵的上三角部分否则每个元素会被交换两次结果又变回原样。这一步可以用生活里的动作来想把整个矩阵沿着从左上角到右下角的那条对角线“对折”一下。对折之后原来在右上的元素跑到左下原来左下的元素跑到右上行列坐标互换。4.2 水平翻转每行左右对折把 matrix[i][j] 与 matrix[i][n-1-j] 交换列只遍历到 n//2 即可。这两步分开看都很简单合起来却是一个完整的顺时针旋转这种“简单操作的复合”在算法里很常见。4.3 为什么这两步等于顺时针旋转 90 度用下标推导最清楚。原矩阵元素 (i,j)转置后到 (j,i)再水平翻转行号不变列号变成 n-1-i最终位置是 (j, n-1-i)。对照第 1 节推导的顺时针映射i,j-(j, n-1-i)完全一致。注意顺序不能反。先水平翻转再转置得到的是逆时针 90 度因为 (i,j) 先变成 (i,n-1-j)再转置变成 (n-1-j,i) 这不是顺时针映射。如果你不想每次推导可以只记一句顺时针 先转置 再左右翻转逆时针 先转置 再上下翻转或者先左右翻转再转置。4.4 完整代码Pythonfrom typing import List def rotate(self, matrix: List[List[int]]) - None: n len(matrix) for i in range(n): for j in range(i 1, n): matrix[i][j], matrix[j][i] matrix[j][i], matrix[i][j] for i in range(n): for j in range(n // 2): matrix[i][j], matrix[i][n - 1 - j] matrix[i][n - 1 - j], matrix[i][j]Javaclass Solution { 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 tmp matrix[i][j]; matrix[i][j] matrix[j][i]; matrix[j][i] tmp; } } for (int i 0; i n; i) { for (int j 0; j n / 2; j) { int tmp matrix[i][j]; matrix[i][j] matrix[i][n - 1 - j]; matrix[i][n - 1 - j] tmp; } } } }4.5 为什么推荐它作为最终答案转置加翻转的时间复杂度 O(n^2)额外空间 O(1)代码量显著小于四元素轮转而且每个步骤的名字本身就是语义先转置再翻转任何面试官一听就懂。更关键的是它的出错率低边界只需要注意一个“j 从 i1 开始”、一个“j n/2”不容易写出越界访问。如果面试官希望看你的代码风格这一版基本是完美答案。5. 三种解法放在一起怎么选5.1 横向对比表解法时间复杂度额外空间原地性代码量面试友好度辅助数组法O(n^2)O(n^2)否少用于铺垫四元素轮转O(n^2)O(1)是中考细节转置加翻转O(n^2)O(1)是最少强烈推荐时间复杂度上三者没有区别空间上后两者严格优于辅助数组。如果你在本地刷题只是为了 AC解法三的简单直接最有优势如果你是为了面试三种都要能写出来因为面试官经常会追问优化过程。5.2 面试表述的推荐节奏我会这样讲首先给出辅助数组方案明确说明它的优点是直观缺点是空间 O(n^2)然后主动提出可以压缩空间引出四元素轮转或转置翻转最后补一句“还有一种更简洁的做法是先转置再左右翻转”。这样一套下来既展示了从暴力到优化的思维过程又展示了代码能力。不要一上来就写转置翻转有些面试官会觉得你在背题先把映射关系讲清楚再优化观感完全不同。换句话说这道题不仅考你会不会写还考你会不会“讲”。5.3 旋转方向的扩展如果题目改成逆时针 90 度用转置翻转只是把顺序换成“水平翻转 转置”即可如果改成 180 度可以直接做两次行列交换甚至两次水平翻转再两次垂直翻转也可以理解成“每行反转 每列反转”。熟练掌握了映射这类变形题基本三分钟写出。另外你会发现解法二的四元素轮转也是可以扩展的逆时针旋转只需要把赋值反向循环一次180 度旋转则只需要交换两个对角元素。这些变形都在同一个框架下不建议单独背。5.4 一行 zip 技巧与非方阵的现实延伸Python 里有一个很容易让人眼前一亮的写法list(zip(*matrix[::-1]))它能把矩阵逆序后再打包转置得到的就是顺时针旋转后的新矩阵。但它有一个致命限制返回的是新的二维结构元组组成的列表完全不符合“原地修改”的要求。所以这个写法在 LeetCode 48 上不能直接用只能在“允许返回新矩阵”的题目里玩梗或者做快速验证。另外LeetCode 48 明确约束是 n × n 方阵所以原地旋转才可行。真实世界的图像一般是 width × height 的长方形直接原地旋转不可行需要额外的转置缓冲或处理策略。如果面试中遇到“矩阵旋转”的变体题第一件事永远是确认矩阵是否方阵、是否允许额外空间、返回值是原地还是新数组。这些约束直接决定用什么解法。6. 最容易翻车的细节与我的调试习惯6.1 转置时的双重循环起点for j in range(n) 会导致交换两次矩阵没有变化。而且这个 bug 不会报错很坑。所有转置类操作的统一口诀右上三角j 从 i1 开始。有些同学会写成for j in range(i, n)这样对角线元素会被交换一次看起来好像没区别但因为在原地交换时对角线元素与自身交换不影响结果所以从 i 或 i1 开始本质上没问题。但从标准转置定义看j 从 i1 开始更干净也能减少一次无意义的自交换。6.2 原地旋转的圈层循环外圈循环 range(n//2)如果写成 n 会重复处理中心元素或用错误坐标越界。写成 n-1 则会少处理最内层的一圈。这里最稳的记忆方式每一层都要留下一个中心位置不处理所以循环次数恰为 n 的一半向下取整。6.3 Python 中 List 引用的真相Python 列表存储的是对象的引用matrix 的每一行是一个列表对象。二维列表做浅拷贝时比如matrix.copy()只会复制外层列表内层行列表仍然共享引用。一旦修改内层任何一个元素另一个“拷贝”也会跟着变这是很多人用 Python 写矩阵题目时经常遇到的神秘现象。更安全的拷贝方式有copy.deepcopy(matrix)但代价很大。在本题的辅助数组版本里直接用列表推导式[[0] * n for _ in range(n)]生成新表而不是用乘法[[0] * n] * n——后者会让每一行都指向同一个列表对象改一行等于改所有行属于必背的 Python 大坑。6.4 Java 中数组拷贝的正确姿势Java 二维数组int[][]是引用类型int[][] copy matrix不复制内容Arrays.copyOf也只能浅拷贝一层matrix.clone()同样如此。真正安全的拷贝需要两层循环或者System.arraycopy对每一行执行。在解法一中我用了matrix[i] rotated[i].clone()这相当于把每一行的一维数组内容复制到新的行数组中效率不错语义也清晰。如果你在面试中写出matrix[i] rotated[i]也说得通但最好补一句“这是替换行引用”显示你对 Java 数组模型理解到位。6.5 手写用例的习惯我每次写完都会快速跑 1x1、2x2、3x3、4x4 四组用例。2x2 只有四个角最容易暴露赋值方向错3x3 有中心元素最容易暴露冗余重复处理4x4 有多个 offset最容易暴露边界值。在 OJ 上提交前先花十秒在纸上把 3x3 走通能省掉一次 WA。比如 3x3 矩阵中心元素 5 在旋转后仍然在中心肉眼就能判断有没有被误处理四角上的 1、3、9、7 如果方向错了立刻能看出来。这个习惯在 LeetCode 的矩阵类题目里非常通用尤其是旋转矩阵、螺旋矩阵、生命游戏这类题目手推一个小例子比看半天调试日志都快。老实说LeetCode 48 这道题我刷过不止一遍每次换一种解法重新写都会发现新的细节。后来我给自己定了个规矩凡是矩阵题先写映射公式再动手写代码。解法三虽然短但如果你不能在三分钟内从映射公式推导出来说明还没吃透真正面试的时候脑子里只有一段背诵的代码是很危险的。建议你也试试用十分钟把三种解法各写一遍把这里的 3x3、4x4 手推一遍之后再遇到旋转类题目会顺手很多。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价