最近有朋友跑来问我,说在刷题时碰到“顺时针旋转矩阵”这道题,网上一搜题解一大把,但要么是直接给代码让人背,要么讲得云里雾里,看完还是不明白为什么这么写。这题确实很经典,LeetCode上叫“旋转图像”,面试出现频率极高,但很多人只是背下了解法,没搞懂背后的“模拟”思路到底是怎么来的。
其实“顺时针旋转矩阵”本质上是模拟题里非常典型的一类:规则清晰、实现细节多、边界条件容易踩坑。如果你能彻底吃透这道题,不只是会背代码,而是理解它背后的“模拟思维”,那以后再遇到螺旋矩阵、矩阵逆时针旋转、原地转置这类问题,基本都能举一反三。
这篇文章我就从这道题出发,把完整的思考链路、几种典型解法、为什么某些解法看着巧妙但容易写错、以及如何把“模拟”的思路迁移到其他场景,一次性讲透。不管你是在刷题准备面试,还是工作中遇到矩阵变换需求,这篇都值得认真读完。
1. 问题定位:这个经典题到底在考什么
先明确题目本身。输入是一个 n x n 的二维矩阵,要求将它顺时针旋转90度,而且必须在原地修改,不能额外开一个等大小的矩阵来存结果。
以 3x3 矩阵为例:
code复制1 2 3
4 5 6
7 8 9
顺时针旋转90度后变成:
code复制7 4 1
8 5 2
9 6 3
很快能看出规律:原矩阵第 0 行变成了新矩阵的第 n-1 列,原矩阵第 1 行变成了新矩阵的第 n-2 列,以此类推。用坐标表达就是:(i, j) 位置的元素,会移动到 (j, n-1-i) 位置。
这个题的考点非常多,逐个拆开看:
1.1 考的是“会不会建立坐标映射”
这是最核心的一层。很多人第一次做这题,能凭直觉写出 res[j][n-1-i] = matrix[i][j],这说明你理解了旋转的本质是“坐标映射”。但面试官紧接着会问:如果不能用额外矩阵呢?这时候很多人就卡住了。
1.2 考的是“原地操作的循环覆盖逻辑”
原地旋转,意味着你不能把元素搬到新矩阵再搬回来,而是要在当前矩阵上通过连续交换完成旋转。本质上是一次“循环移位”:a -> b -> c -> d -> a,四个位置轮着覆盖。问题在于:哪些元素是一组的?每组的起始位置怎么确定?外层循环和内层循环的边界到底怎么算?
1.3 考的是边界条件的敏感度
n 是奇数、n 是偶数,处理范围是否不同?外层处理多少圈?每一圈内要处理多少个元素?这些细节如果没想清楚,写出来的代码几乎必然有 bug。
1.4 考的是不同解法之间的思维切换
同一种问题,至少有三类常见解法:新矩阵映射、原地四元素交换、先转置再翻转。这三种解法的时间和空间复杂度不同,代码量也不同,面试时你选哪种、为什么选这种,本身就是面试官观察你工程判断力的窗口。
所以这道题绝不是“背一个解法”这么简单。接下来我会按“从朴素到最优”的顺序展开,每一步都会说清楚“为什么”。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 为什么“老老实实模拟”不是好解法:初版朴素思路与性能瓶颈
遇到“顺时针旋转矩阵”,很多人的第一反应是:开一个新矩阵,把每个元素映射过去。这个思路完全正确,代码也很简短:
python复制def rotate(matrix):
n = len(matrix)
res = [[0] * n for _ in range(n)]
for i in range(n):
for j in range(n):
res[j][n-1-i] = matrix[i][j]
return res
如果题目没限制空间,这个写法完全没问题,正确性一目了然。但 LeetCode 原题明确要求“原地旋转”,所以必须另想办法。
2.1 朴素方法的时间复杂度其实已经最优
先说明一点:任何解法的时间复杂度都不可能低于 O(n^2),因为 n x n 矩阵里的每个元素都必须被移动到目标位置,这是理论下界。所以朴素方法的 O(n^2) 时间复杂度没有任何问题。
问题出在空间上。额外开一个 n x n 矩阵,空间复杂度是 O(n^2)。当 n 很小的时候无所谓,但到了 n = 1000 甚至更大,多出来的内存就是 10^6 量级的整数存储,在某些嵌入式场景或内存受限环境里,这不可接受。
2.2 朴素的“值交换”思路为什么能省掉新矩阵
原地旋转的核心思路是:旋转操作实际上是把元素分成若干个“环”,每个环内做循环移位。
以 4x4 矩阵为例:
code复制1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16
最外层的环是:
code复制1 2 3 4
5 8
9 12
13 14 15 16
顺时针旋转90度后,这个环上的元素会怎么走?看几个关键位置:
1应该去4的位置4应该去16的位置16应该去13的位置13应该去1的位置
这四个位置形成一个闭环:(0,0) -> (0,n-1) -> (n-1,n-1) -> (n-1,0) -> (0,0)。
再看另一组:
2应该去8的位置8应该去15的位置15应该去9的位置9应该去2的位置
四元素一组,轮换着覆盖。只要把每组的四个元素按顺序交换,就能完成整圈的旋转,而且不需要额外矩阵。
2.3 朴素思路落地时最容易踩的坑
这里有个特别容易犯的错:用临时变量保存值时,覆盖顺序搞反。
很多人会写类似这样的逻辑:
python复制temp = matrix[i][j]
matrix[i][j] = matrix[n-1-j][i] # 错误:这不是 (i,j) 的逆映射
matrix[n-1-j][i] = matrix[n-1-i][n-1-j]
# ... 越写越乱
为什么乱?因为你必须明确每个位置的“来源”和“去向”。正确做法是沿着环的逆方向做覆盖:先把目标位置的值存起来,再把当前位置的值赋过去。
四元素一组的标准写法:
python复制temp = matrix[i][j]
matrix[i][j] = matrix[n-1-j][i]
