LeetCode 第 48 题「旋转图像」
字数 2358 2025-10-25 23:45:28
好的,我们这次来详细讲解 LeetCode 第 48 题「旋转图像」。
1. 题目描述
给定一个 \(n \times n\) 的二维矩阵 matrix 表示一个图像,请你将图像 顺时针旋转 90 度。
要求:
- 必须在 原地 旋转图像,这意味着你需要直接修改输入的二维矩阵。
- 不要使用另一个矩阵来旋转。
示例 1:
输入:matrix = [[1,2,3],
[4,5,6],
[7,8,9]]
输出:[[7,4,1],
[8,5,2],
[9,6,3]]
示例 2:
输入:matrix = [[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]]
约束条件:
- \(n = \text{matrix.length} = \text{matrix}[i].\text{length}\)
- \(1 \le n \le 20\)
- \(-1000 \le \text{matrix}[i][j] \le 1000\)
2. 直观理解旋转规律
我们以 3×3 矩阵为例:
原始矩阵:
(0,0) (0,1) (0,2) 1 2 3
(1,0) (1,1) (1,2) 4 5 6
(2,0) (2,1) (2,2) 7 8 9
顺时针旋转 90° 后:
(0,0) (0,1) (0,2) 7 4 1
(1,0) (1,1) (1,2) 8 5 2
(2,0) (2,1) (2,2) 9 6 3
观察对应关系:
- 原来
(0,0)的 1 到了(0,2) - 原来
(0,2)的 3 到了(2,2) - 原来
(2,2)的 9 到了(2,0) - 原来
(2,0)的 7 到了(0,0)
这其实是一个 四元素循环交换。
3. 推导坐标映射公式
我们找规律:对于矩阵中任意一点 (i, j),旋转 90° 后它的新位置是?
先看 3×3 矩阵:
(0,0) → (0,2)(0,1) → (1,2)(0,2) → (2,2)(1,0) → (0,1)(1,1) → (1,1)(中心点不变)(1,2) → (2,1)(2,0) → (0,0)?等等,不对,应该是(2,0) → (0,0)吗?检查:原来(2,0)是 7,旋转后它在(0,0)?不对,7 在旋转后到了(0,0)?我们重新验证:
实际旋转后:
- 原
(0,0)=1到(0,2) - 原
(0,1)=2到(1,2) - 原
(0,2)=3到(2,2) - 原
(1,0)=4到(0,1) - 原
(1,1)=5到(1,1) - 原
(1,2)=6到(2,1) - 原
(2,0)=7到(0,0) - 原
(2,1)=8到(1,0) - 原
(2,2)=9到(2,0)
所以归纳规律:
对于位置 (i, j),旋转 90° 后的新位置是 (j, n-1-i)。
验证:
(0,0)→(0, 3-1-0) = (0,2)✅(0,1)→(1, 3-1-0) = (1,2)✅(2,0)→(0, 3-1-2) = (0,0)✅(2,2)→(2, 3-1-2) = (2,0)✅
4. 四位置循环交换
如果我们直接按 (i,j) 到 (j, n-1-i) 去赋值,会覆盖掉原来的值。所以要用一个 临时变量 做四位置循环交换。
对于每个 (i, j),四个相关位置是:
(i, j)(j, n-1-i)(n-1-i, n-1-j)(n-1-j, i)
验证这个循环:
- 从位置 1 到位置 2:
(i,j) → (j, n-1-i) - 从位置 2 到位置 3:
(j, n-1-i) → (n-1-i, n-1-j) - 从位置 3 到位置 4:
(n-1-i, n-1-j) → (n-1-j, i) - 从位置 4 到位置 1:
(n-1-j, i) → (i, j)✅
所以交换顺序(逆时针或顺时针赋值)都可以,只要一次完成四个位置的循环交换。
5. 遍历的范围
我们不需要遍历所有 (i, j),因为每个四元组交换会处理 4 个元素,遍历整个矩阵会重复交换。
对于 \(n\) 为偶数(如 4×4):
- 遍历行
i从0到n/2 - 1 - 遍历列
j从i到n-1-i-1(即内层每行少两个元素)
对于 \(n\) 为奇数(如 3×3):
- 中心点
(n/2, n/2)不需要交换,所以遍历行i从0到n/2 - 1(整数除法),列范围同上。
实际上可以统一为:
for i in range(0, n // 2):
for j in range(i, n - 1 - i):
交换四元组
6. 算法步骤
- 设
n = len(matrix) - 对于
i从0到n//2 - 1: -
对于 `j` 从 `i` 到 `n-2-i`(即 `n-1-i` 的前一个): -
临时存储 `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`
7. 举例说明(3×3)
初始:
1 2 3
4 5 6
7 8 9
i=0, j=0 到 n-2-0=1(即 j=0,1? 不对,n=3,n-1-i=2,j 从 0 到 1)
j=0:
四位置:
- (0,0)=1
- (0 对应 n-1-j=2, i=0) → (2,0)=7
- (n-1-i=2, n-1-j=2) → (2,2)=9
- (j=0, n-1-i=2) → (0,2)=3
交换顺序(逆时针赋值):
1 → 7 的位置,7 → 9 的位置,9 → 3 的位置,3 → 1 的位置。
结果:
7 2 1
4 5 6
9 8 3
j=1:
四位置:
- (0,1)=2
- (n-1-j=1, i=0) → (1,0)=4
- (n-1-i=2, n-1-j=1) → (2,1)=8
- (j=1, n-1-i=2) → (1,2)=6
交换:
2→4, 4→8, 8→6, 6→2
结果:
7 4 1
8 5 2
9 6 3
已经完成。
8. 代码实现(Python)
def rotate(matrix):
n = len(matrix)
for i in range(n // 2):
for j in range(i, n - 1 - i):
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
9. 另一种思路(转置 + 左右翻转)
顺时针旋转 90° 等价于:
- 先转置矩阵(行列互换)
- 再将每一行反转
步骤:
原始:
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
代码更简洁:
def rotate(matrix):
n = len(matrix)
# 转置
for i in range(n):
for j in range(i, n):
matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j]
# 每行反转
for i in range(n):
matrix[i].reverse()
10. 总结
- 方法一(四位置循环交换)是原地操作,理解坐标映射是关键。
- 方法二(转置+反转)更直观易懂,同样满足原地修改(因为转置和反转都可原地)。
- 两种方法时间复杂度都是 \(O(n^2)\),空间复杂度 \(O(1)\)。
这样循序渐进地讲解,你理解了吗?