资讯动态

LeetCode 48 旋转图像:坐标映射与原地旋转的两种高效解法

发布时间:2026/10/20 13:03:45 来源:尧图企业网站定制
刷题刷到第48题很多人的第一反应是“哦就是那个旋转图像的题”。确实LeetCode 48 旋转图像几乎出现在每一份算法面试清单里但真正在面试白板上写的人十个里有三四个会在坐标映射上卡住。这道题不涉及动态规划不涉及复杂数据结构矩阵操作和下标推导就是全部属于那种“看起来简单、写起来绕、考完还想骂”的典型题目。这篇文章把我自己从背答案到真正上手的路径复刻出来把暴力复制、原地分圈、转置翻转三条路线讲清楚也会说一下测试时踩过的坑希望能让你一次吃透。1. 题目到底在考什么一道“旋转”题背后的核心考点1.1 题目原文与示例回顾题面非常短给定一个 n × n 的二维矩阵 matrix 表示一个图像把这个图像顺时针旋转 90 度并且要求原地旋转也就是直接修改输入的二维矩阵不要使用另一个矩阵来承接。示例输入 1 2 3 4 5 6 7 8 9 输出 7 4 1 8 5 2 9 6 3再看一个 4 × 4 的输入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观察坐标变化前我建议你先自己动手把这两个例子走一遍。你会发现规律其实很朴素旋转前的第 0 行变成了旋转后的最后一列旋转前的第 1 行变成了倒数第二列但列内元素的顺序是反的。这只是表象真正稳定的说法是坐标映射原来在 (row, col) 位置的元素顺时针旋转 90 度后会跑到 (col, n - 1 - row) 位置。这个公式是全题的命根子无论你后面用哪种写法最后都要回到这个映射上。1.2 出题人真正想考察的四个点第一抽象建模能力。一个图片旋转问题能不能快速把它翻译成二维数组下标变换。这不是记忆力问题而是你有没有处理过矩阵坐标变换的思维框架。第二原地修改的意识。题目明确要求不要用额外矩阵这就是在逼你在空间复杂度上做文章。很多人在第一步就崩了直接新建一个二维数组往回填这叫“结果对了但你根本没按题目要求做”。面试官会追问你要当场拿出原地方案。第三边界处理能力。n 是奇数还是偶数处理逻辑会略有不同n1 的时候程序不能崩溃每次循环的起止边界写错矩阵就永远旋转不成功而且看起来很诡异。第四工程直觉。代码能不能让别人一眼看懂为什么内层循环跑 len(matrix[0]) 而不是 len(matrix)这些都是实战中真正会遇到的细节。所以别看这道题只有“原地旋转”四个字它实际上已经把矩阵题的几个核心难点都覆盖了。理解了这四点你后面看任何矩阵旋转、矩阵翻转、螺旋遍历的题都会顺畅很多。2. 暴力解法不是玩具先会复制再谈优化2.1 最简单也最容易出错的实现很多人一上来就追求原地结果越写越乱。我的建议是先写一版“开新数组”的解法哪怕最后不提交也一定要在草稿纸上推一遍。它最大的价值是让你迈过“坐标映射”这道坎。新建一个同样大小的 result 矩阵遍历原矩阵的每个位置 (i, j)把元素填到目标位置。依据前面说的映射关系def rotate_bruteforce(matrix): n len(matrix) result [[0] * n for _ in range(n)] for i in range(n): for j in range(n): result[j][n - 1 - i] matrix[i][j] return result跑一下示例[1, 2, 3] [7, 4, 1] [4, 5, 6] - [8, 5, 2] [7, 8, 9] [9, 6, 3]这个版本的关键就这一行result[j][n - 1 - i] matrix[i][j]。只要你能读明白它后面所有优化都是在同一个公式上打转。2.2 暴力解法的时间和空间代价时间复杂度显然是 O(n^2)因为必须访问每一个元素空间复杂度也是 O(n^2)因为开了一个同样大小的矩阵。这个版本的问题不是逻辑而是空间。这里有两个容易被忽略的坑。第一个是 Python 的二维数组初始化写法。[[0] * n] * n看起来没错但实际每一行是同一个列表对象的引用修改一行会串到所有行。正确写法是列表推导式[[0] * n for _ in range(n)]。第二个坑是很多人在自己的代码里直接用matrix接结果比如matrix result这确实让外部变量指向了新矩阵可原来传入的矩阵内容并没有被改动严格来说不满足“原地修改”的要求。暴力版本虽然在空间上不合格但它帮我们锁定了一个重要结论坐标映射公式本身是对的只是需要找一个不需要新数组的方式来执行。有这条线索再去看原地旋转就不会觉得抽象了。3. 原地旋转分圈处理才是正解3.1 旋转过程的手工推导原地旋转的经典思路是把矩阵看成一圈一圈的洋葱从外到内一层一层转。最外层是边界元素往里一层规模减 2直到最中心。外层旋转的本质是什么每一圈上四个边的对应元素做轮换。拿 4 × 4 矩阵第一圈的左上角那组举例5 1 9 11 2 4 8 10 13 3 6 7 15 14 12 16取第一圈的四个角左上 5、右上 11、右下 16、左下 15。顺时针旋转 90 度之后5 应该去右上角11 应该去右下角16 应该去左下角15 应该去左上角。也就是说这 4 个位置的元素要同时顺时针轮转。不只是角每条边中间的元素也是同样的轮转模式。所以问题可以拆成对当前这一圈从顶行的第一个元素开始依次取出四个对应位置做一次轮换然后再往后挪一个位置再轮换直到这一圈的起点和终点重叠。外层处理完缩进一层继续。3.2 四指针循环的标准写法按这个思路最经典的标准写法如下def rotate(matrix): n len(matrix) # 从外层到内层只需要转 n // 2 圈 for i in range(n // 2): # 当前这一圈的有效长度 for j in range(i, n - i - 1): # 四个对应位置 temp matrix[i][j] matrix[i][j] matrix[n - 1 - j][i] # 左 - 上 matrix[n - 1 - j][i] matrix[n - 1 - i][n - 1 - j] # 下 - 左 matrix[n - 1 - i][n - 1 - j] matrix[j][n - 1 - i] # 右 - 下 matrix[j][n - 1 - i] temp # 上 - 右这一段初看会有点晕但只需要盯住一个概念四步逆时针取值。这里我用的是把“上”“左”“下”“右”四个位置逆时针倒着取出来最后再赋给目标的技巧。也就是先存顶行元素然后把左边元素搬到顶行下面元素搬到左边右边元素搬到下面最后把存的顶行元素放到右边。这样一次就能让一组元素完成顺时针腾挪。最外层的i表示当前圈层的起始行和起始列范围是range(n // 2)。为什么不是n因为转圈是一层一层往内缩的最里面那层如果只剩一个元素不需要动如果是两层n//21只处理外层内层自然在下一轮 j 的范围里消失。里面的j从i起步到n - i - 1结束注意是左闭右开这保证了当前圈中每条边只处理“去掉角落那组”之后剩余的部分但又恰好能把每条边的所有元素都覆盖到。我用 3 × 3 矩阵走一遍i 0, j 0 temp matrix[0][0] 1 matrix[0][0] matrix[2][0] 7 matrix[2][0] matrix[2][2] 9 matrix[2][2] matrix[0][2] 3 matrix[0][2] temp 1 结果第一组角完成 7 2 1 4 5 6 9 8 3接着i 0, j 1 temp matrix[0][1] 2 matrix[0][1] matrix[1][0] 4 matrix[1][0] matrix[2][1] 8 matrix[2][1] matrix[1][2] 6 matrix[1][2] temp 2最终得到7 4 1 8 5 2 9 6 3正好是题目给的预期输出。这个推导过程很重要建议自己拿笔走一遍 3×3 和 4×4走顺了再写代码。3.3 下标公式的记忆套路每次写这题都要现推四个下标确实容易慌。我自己的记忆方法是这样先记住左上角位置是(i, j)右上角是(j, n-1-i)右下角是(n-1-i, n-1-j)左下角是(n-1-j, i)。这个规律其实就是四个顶点绕着中心逆时针走。你只要在纸上画一个方框写上 i、j然后逆时针把四个角标出来再根据坐标轴方向写出对应坐标一分钟就能推出来。比起死记硬背画画更快也更稳。原地分圈写法的时间复杂度 O(n^2)空间复杂度 O(1)这是这道题最主要的合格解。4. 另一种妙解先转置再水平翻转4.1 为什么先转置再翻转能旋转90度除了分圈轮换还有一种技巧性更强但也很好用的思路把旋转操作拆成两个主对角线镜像操作。第一步沿着主对角线做转置也就是matrix[i][j]和matrix[j][i]交换。转置之后原本的第 0 行变成了第 0 列第 1 行变成了第 1 列所有元素都沿主对角线对调。第二步对每一行做水平翻转也就是把matrix[i][j]和matrix[i][n - 1 - j]交换。为什么这两步合起来就能达到顺时针旋转 90 度可以先这样直观理解转置让行变列相当于把图像沿左上到右下的对角线翻折水平翻转再让所有列的顺序反向。两个镜像变换叠加起来的效果正好是一个顺时针 90 度旋转。这在几何上非常自然两次沿不同轴的反射等于一次旋转。用 3×3 验证原始 1 2 3 4 5 6 7 8 9 转置 1 4 7 2 5 8 3 6 9 每行水平翻转 7 4 1 8 5 2 9 6 3和旋转变换完全一致。4.2 代码实现及注意事项这种思路的代码结构比圈层轮换更直观两道简单的循环就搞定def rotate(matrix): 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): left, right 0, n - 1 while left right: matrix[i][left], matrix[i][right] matrix[i][right], matrix[i][left] left 1 right - 1几个关键点转置时内层循环从i 1开始不是从0开始。因为对角线上的元素交换后不变而且如果你从 0 开始每个元素会交换两次等于没换。翻转操作可以用双指针也可以用切片但切片在 Python 里会产生新列表不够“原地”面试时最好写双指针版本。这个方法的复杂度同样是 O(n^2) 时间和 O(1) 空间。4.3 两种原地方案的对比与选型维度分圈轮换转置 水平翻转理解难度坐标映射较复杂容易下标写错拆分后每一步语义清晰几乎不会记错代码量逻辑集中边界处理需要仔细两段独立逻辑维护性好面试表现能体现对矩阵结构的深度理解能体现数学化简能力出错点四个下标容易写混转置范围、翻转方向容易遗漏如果是面试现场我一般建议先想到哪种就写哪种但写完一定用 2×2 或 3×3 的用例手算一遍。对初学者来说转置翻转的出错率远低于分圈轮换因为它把一个大问题拆成了两个能独立验证的小问题。工程上我更倾向于这种写法可读性更好review 的人不用为了你的四个下标推导半天。5. 实战中的延伸非方阵、逆时针、批量小技巧5.1 非方阵怎么办LeetCode 48 明确要求 n×n 方阵但真实项目里图片和数据集很少有恰好方阵的情况。处理 m×n 的矩阵想顺时针旋转 90 度结果会变成 n×m所以不可能在原来的结构上原地完成必须新建一个新尺寸的矩阵。通用写法def rotate_rectangle(matrix): rows len(matrix) cols len(matrix[0]) result [[0] * rows for _ in range(cols)] for i in range(rows): for j in range(cols): result[j][rows - 1 - i] matrix[i][j] return result注意这里目标矩阵行数是原列数列数是原行数坐标里用的是rows - 1而不是n - 1。遇到非方阵先和面试官确认能不能开新数组通常都会允许因为原地物理上不可能。5.2 逆时针旋转的对应思路逆时针 90 度同样可以拆操作。如果是转置派逆时针等于先转置再做垂直翻转也就是每一列上下翻转。如果你更习惯直接写坐标逆时针的映射是(row, col) - (n - 1 - col, row)把它改成分圈轮换也完全可行四步操作的方向换成顺时针取数即可。我偏好一个更通用的封装写一个函数通过参数控制旋转方向。def rotate_general(matrix, clockwiseTrue): 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] if clockwise: # 水平翻转 for i in range(n): matrix[i].reverse() else: # 垂直翻转 for i in range(n // 2): matrix[i], matrix[n - 1 - i] matrix[n - 1 - i], matrix[i]这样一次把旋转方向都覆盖了。顺手写出来面试官会觉得你对这类变换有系统理解。5.3 扩展矩阵旋转在真实项目中的映射这道题虽然出在算法题库但实际应用场景非常密集。二维图像像素矩阵的旋转是图片编辑工具的基础操作游戏里的地图块、棋盘类数据在展示前做旋转对齐表格导出报表时把行转列再横向输出本质就是转置加翻转甚至某些数据增强工具在训练图像模型时也会把整批图片做 90 度旋转来扩增样本。我自己的体会是真正工作时很少直接手写旋转逻辑大都有现成库但遇到自定义数据格式、流式处理、或者移动端内存受限的场景你就必须明白底层坐标是怎么换的。记忆公式没用理解推导过程才能在各种变体里快速改代码。6. 常见问题与排查技巧实录6.1 高频Bug下标越界和漏转下标越界通常出现在把 n 写成len(matrix[0])。方阵时两者没区别但如果你按非方阵的思路去写或者测试用例是 1×n 的矩阵问题就暴露了。建议统一用len(matrix)作边长。漏转的典型症状是矩阵旋转之后和原样一模一样。最常见的原因是转置的内层范围写错比如写成了range(n)导致每对元素交换两次等于没交换。另一种情况是分圈写法里j的范围误写成range(n)导致每条边里的元素被重复轮换。重复轮换和只轮换一半在视觉上都是“矩阵几乎没变”尤其 2×2 的用例中很难看出来所以一定拿 3×3 甚至 4×4 的用例验证。还有一个隐蔽的坑Python 里对行做matrix[i] reversed(matrix[i])这其实生成了一个新的列表并赋值给当前行虽然是局部操作但严格来说不是所有语言都能接受这种写法。面试时尽量用双指针交换避免节外生枝。6.2 面试场景下的答题节奏我的建议是先和面试官确认“矩阵一定是方阵吗可以额外开辟空间吗”问清楚需求避免做完才发现理解偏差。先给暴力解法哪怕知道要优化。它能帮助你顺利引出坐标映射公式也给后续优化做铺垫。写原地方案前在白板上画一个小方阵把四个顶点的坐标圈出来再口头推一遍下标关系。别急着写代码写错再改更浪费时间。代码写完用 n1、n2、n3 三个用例分别自测。n1 验证不死循环n2 验证最小有效旋转n3 验证奇数边长中心元素是否受影响。最后自己主动提复杂度“时间 O(n^2)空间 O(1)”不用等面试官问。6.3 避坑清单问题原因解决方案结果与输入一模一样转置范围错交换两次内层从 i1 开始矩阵只转了一半内层 j 范围多算或少算j 取 range(i, n-i-1)使用额外数组返回没读懂“原地”要求改为交换操作3×3 中间元素被改变内层循环边界不正确边界用 n-i-1 排除中心每行翻转时漏了中间列while left right 条件写错用双指针左闭右开即可把 len(matrix[0]) 当边长方阵习惯但矩形会崩统一用行数 len(matrix)说实话这题的 bug 类型翻来覆去就这几种每一个我都踩过。第一次写分圈轮换时我把n - 1 - i写成了n - i结果用 3×3 用例测试时数组下标越界改成n - 1 - i才对。从那以后我每次都会先在草稿纸上用具体数字推算一遍再动键盘。我个人在实际操作中的体会是这题没必要背任何一套解法更不需要囤多种语言的模板。真正管用的是把旋转拆成“坐标映射”这一个核心然后自己推一遍四个角的位置变化。推过之后分圈写法自然就出来了转置翻转只是锦上添花。后续遇到 90 度旋转的变体题、螺旋矩阵、对角线遍历你会发现全是在同一套坐标关系上做文章。能把一道经典题吃透到这个程度比盲目刷十道类似的题要值得多。

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

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

免费获取报价 →
↑