48. 旋转图像 (Medium)
专题归类: 矩阵 LeetCode 链接: https://leetcode.cn/problems/rotate-image/
在线做题: 写 Python 代码并运行 · Java 跳转 LeetCode
题目描述
给定一个 n × 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 == matrix.length == matrix[i].length1 <= n <= 20-1000 <= matrix[i][j] <= 1000- 只能原地修改,不能使用额外的矩阵
题目详细分析
数据范围分析:
- n 最大 20,矩阵最多 400 个元素,非常小。但作为经典面试题,O(n²) 时间 + O(1) 空间是最优解。
- 元素值范围 -1000~1000,不重要。
输入输出特征:
- 输入是 n×n 方阵(注意不是 m×n),必须是方阵才能旋转。
- 顺时针旋转 90 度:位置
(i, j)旋转到(j, n-1-i)。
边界条件:
- n=1 → 矩阵只有一个元素,旋转后不变。
- n=2 → 最小可旋转的矩阵(2×2)。
- 矩阵元素可以重复,不影响旋转逻辑。
隐藏条件:
- 原地操作是核心约束,意味着不能创建新矩阵然后复制。
- 矩阵旋转本质上是一组「四元素交换」:四个对称位置的元素循环交换。
- 旋转可以拆解为两个更简单的操作组合:转置 + 翻转。
小白版直白理解
想象你有一张正方形的照片,你想把它顺时针转 90 度。
笨办法: 拿一张新的空白照片纸,把每个像素复制到新位置。但这需要额外一张纸。
聪明办法(两步法):
- 沿对角线翻折(转置):就像把书页沿左上到右下的中线对折——
(i,j)和(j,i)交换。 - 沿垂直中线左右翻折(每行反转):就像合上书本一样,把每一行左右颠倒。
经过这两步,照片就神奇地旋转了 90 度!
生活中的类比:
- 你有一个装满数字的方盒子,想把它侧过来看
- 就像做魔方的一个面旋转
- 就像在纸上写字后把纸转 90 度阅读
解题思路
思路一:转置 + 翻转(推荐)
核心洞察:
顺时针旋转 90 度可以拆解为两个简单步骤:
- 转置:沿主对角线(左上到右下)翻转,即
matrix[i][j]与matrix[j][i]交换 - 翻转:每行逆序(左右翻转),即
matrix[i].reverse()
数学原理:
转置: (i, j) → (j, i)
翻转: (i, j) → (i, n-1-j)
组合: (i, j) → (j, i) → (j, n-1-i) ✓ 这就是顺时针旋转 90 度的公式
def rotate(matrix):
n = len(matrix)
# Step 1: 转置(沿主对角线翻转)
# 注意 j 从 i+1 开始,只遍历上三角,避免重复交换
for i in range(n):
for j in range(i + 1, n):
matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j]
# Step 2: 每行逆序(左右翻转)
for i in range(n):
matrix[i].reverse()复杂度: O(n²) 时间,O(1) 空间。
思路二:四元素环状交换(一次旋转四个角)
核心洞察:
旋转 90 度时,四个对称位置的元素循环交换:
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
只需要遍历左上角的 1/4 区域(上三角的一部分),每个元素一次旋转 4 个位置。
def rotate_cycle(matrix):
n = len(matrix)
for i in range(n // 2):
for j in range((n + 1) // 2):
# 四元素交换
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从 0 到n // 2 - 1(上半部分行)j从 0 到(n + 1) // 2 - 1(左半部分列)- n 为奇数时,中心元素不需要旋转
思路三:使用额外矩阵(不符合题意但最易理解)
创建一个新矩阵,把每个元素放到旋转后的位置,然后复制回原矩阵。不符合 O(1) 空间要求。
def rotate_copy(matrix):
n = len(matrix)
rotated = [[0] * n for _ in range(n)]
for i in range(n):
for j in range(n):
rotated[j][n - 1 - i] = matrix[i][j]
for i in range(n):
for j in range(n):
matrix[i][j] = rotated[i][j]易错点
- 转置的遍历范围:
j从i+1开始,只遍历上三角(或下三角),不要从 0 开始,否则会交换两次回到原样。 - 逆时针旋转:先翻转后转置(而不是先转置后翻转)。
- n 为奇数:中心元素在旋转中不变,但循环覆盖时要确保它不被覆盖到也不需要旋转。
- 反转 vs 逆序:在 Python 中
matrix[i].reverse()是原地反转,matrix[i] = matrix[i][::-1]会创建新列表。 - 循环覆盖的范围:四元素环状交换法中的
range((n + 1) // 2)对奇偶 n 的处理,容易搞错。 - 旋转公式混淆:顺时针 90 度是
(i, j) → (j, n-1-i),顺时针 180 度是(i, j) → (n-1-i, n-1-j),逆时针 90 度是(i, j) → (n-1-j, i)。
框架提炼
矩阵旋转通用模板(转置 + 翻转):
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]
# 翻转(根据需要选择方向)
if 顺时针90度:
for i in range(n):
matrix[i].reverse() # 左右翻转
elif 逆时针90度:
for row in matrix:
row.reverse() # 先上下翻转
# 或者直接转置后上下翻转
elif 顺时针180度:
# 上下翻转 + 左右翻转
matrix.reverse()
for row in matrix:
row.reverse()旋转角度速查表:
| 操作 | 公式 (i,j) → | 拆解 |
|---|---|---|
| 顺时针 90° | (j, n-1-i) | 转置 + 左右翻转 |
| 逆时针 90° | (n-1-j, i) | 左右翻转 + 转置 |
| 顺时针 180° | (n-1-i, n-1-j) | 上下翻转 + 左右翻转 |