LeetCode 48. 旋转图像 - 完整解法详解
一、问题理解
问题描述
给定一个 n × n 的二维矩阵表示一个图像,将图像顺时针旋转 90 度。必须在原地旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要使用另一个矩阵来旋转图像。
示例
python
输入: matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出: [[7,4,1],[8,5,2],[9,6,3]]
解释:
原矩阵:
[1,2,3]
[4,5,6]
[7,8,9]
旋转后:
[7,4,1]
[8,5,2]
[9,6,3]
输入: 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]]
要求
-
原地旋转矩阵,不使用额外空间
-
时间复杂度:O(n²)
-
空间复杂度:O(1)
二、核心思路:数学变换
基本思想
顺时针旋转 90 度可以通过以下两步实现:
-
转置矩阵:将矩阵的行变为列
-
水平翻转:将每一行进行反转
数学原理
对于矩阵中的元素 matrix[i][j],顺时针旋转 90 度后的新位置是 matrix[j][n-1-i]。
具体来说:
-
转置操作:
matrix[i][j]→matrix[j][i] -
水平翻转:
matrix[j][i]→matrix[j][n-1-i]
三、代码逐行解析
方法一:转置 + 翻转(最优解)
Python 解法
python
from typing import List
class Solution:
def rotate(self, matrix: List[List[int]]) -> None:
"""
不要返回任何内容,原地修改矩阵
"""
n = len(matrix)
# 第一步:转置矩阵(行列互换)
# 注意:只需要遍历对角线下方或上方的元素,避免重复交换
for i in range(n):
for j in range(i): # 只遍历对角线下方元素
matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j]
# 第二步:水平翻转每一行
for row in matrix:
row.reverse()
Java 解法
java
class Solution {
public void rotate(int[][] matrix) {
int n = matrix.length;
// 第一步:转置矩阵
for (int i = 0; i < n; i++) {
for (int j = 0; j < i; j++) { // 只遍历对角线下方
int temp = matrix[i][j];
matrix[i][j] = matrix[j][i];
matrix[j][i] = temp;
}
}
// 第二步:水平翻转每一行
for (int i = 0; i < n; i++) {
for (int j = 0; j < n / 2; j++) { // 只遍历前半部分
int temp = matrix[i][j];
matrix[i][j] = matrix[i][n - 1 - j];
matrix[i][n - 1 - j] = temp;
}
}
}
}
方法二:分圈旋转(更直观)
Python 解法
python
from typing import List
class Solution:
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][i]
# 左边移动到上边
matrix[first][i] = matrix[last - offset][first]
# 下边移动到左边
matrix[last - offset][first] = matrix[last][last - offset]
# 右边移动到下边
matrix[last][last - offset] = matrix[i][last]
# 上边移动到右边
matrix[i][last] = top
Java 解法
java
class 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][i];
// 左边移动到上边
matrix[first][i] = matrix[last - offset][first];
// 下边移动到左边
matrix[last - offset][first] = matrix[last][last - offset];
// 右边移动到下边
matrix[last][last - offset] = matrix[i][last];
// 上边移动到右边
matrix[i][last] = top;
}
}
}
}
四、Java 与 Python 语法对比
1. 矩阵操作
| 操作 | Java | Python |
|---|---|---|
| 获取长度 | matrix.length |
len(matrix) |
| 交换元素 | 需要临时变量 | a, b = b, a |
| 反转列表 | 需要手动实现 | list.reverse() |
2. 循环控制
| 操作 | Java | Python |
|---|---|---|
| 遍历行 | for (int i=0; i<n; i++) |
for i in range(n): |
| 遍历对角线下方 | for (int j=0; j<i; j++) |
for j in range(i): |
| 反转数组 | 需要手动实现 | 内置方法 |
3. 矩阵转置
| 操作 | Java | Python |
|---|---|---|
| 转置交换 | 需要临时变量 | 一行交换 |
| 遍历方式 | 双层循环 | 双层循环 |
五、实例演示
示例:matrix = [[1,2,3],[4,5,6],[7,8,9]]
方法一(转置 + 翻转)步骤:
- 原始矩阵:
text
[1, 2, 3]
[4, 5, 6]
[7, 8, 9]
-
第一步:转置矩阵(行列互换):
-
遍历对角线下方元素
-
交换
(1,0)和(0,1):4 ↔ 2 -
交换
(2,0)和(0,2):7 ↔ 3 -
交换
(2,1)和(1,2):8 ↔ 6 -
转置后矩阵:
-
text
[1, 4, 7]
[2, 5, 8]
[3, 6, 9]
-
第二步:水平翻转每一行:
-
翻转第一行:
[1,4,7]→[7,4,1] -
翻转第二行:
[2,5,8]→[8,5,2] -
翻转第三行:
[3,6,9]→[9,6,3] -
最终结果:
-
text
[7, 4, 1]
[8, 5, 2]
[9, 6, 3]
方法二(分圈旋转)步骤:
对于 n=3,只有一个圈(layer=0):
-
first=0,last=2 -
遍历
i=0到1:
当 i=0 时:
-
offset = 0 -
top = 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] = top = 1
此时矩阵:
text
[7, 2, 1]
[4, 5, 6]
[9, 8, 3]
当 i=1 时:
-
offset = 1 -
top = 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] = top = 2
最终矩阵:
text
[7, 4, 1]
[8, 5, 2]
[9, 6, 3]
六、关键细节解析
1. 为什么转置时只遍历对角线下方元素?
-
如果遍历整个矩阵,每个元素会被交换两次,最终回到原位置
-
只遍历对角线下方,确保每个元素只交换一次
-
对角线上的元素不需要交换(自己和自己交换)
2. 为什么转置 + 翻转等于旋转90度?
-
转置:将矩阵的行变为列(相当于沿着主对角线翻转)
-
水平翻转:将每一行左右反转
-
组合效果:顺时针旋转90度
-
验证:
matrix[i][j]→matrix[j][i]→matrix[j][n-1-i]
3. 如何理解分圈旋转中的四个步骤?
对于矩阵中的一组四个元素:
-
上边 → 右边
-
右边 → 下边
-
下边 → 左边
-
左边 → 上边
这形成了一个循环替换,需要用一个临时变量保存第一个元素。
4. 分层旋转中的边界条件是什么?
-
对于
n × n矩阵,有n//2层需要旋转 -
每一层从
first到last-1(不包括最后一个元素) -
最后一个元素会在前一个元素的旋转中被处理
5. 如果 n 为奇数怎么办?
-
中间的元素不需要旋转,保持原位
-
无论是转置+翻转还是分圈旋转,都能正确处理奇数 n
七、复杂度分析
方法一(转置 + 翻转)
-
时间复杂度:O(n²)
-
转置:遍历大约 n²/2 个元素
-
翻转:遍历 n 行,每行 n/2 次交换
-
总操作次数:约 n²
-
-
空间复杂度:O(1)
- 只使用了常数个临时变量
方法二(分圈旋转)
-
时间复杂度:O(n²)
-
每个元素被访问一次
-
总操作次数:n²
-
-
空间复杂度:O(1)
- 只使用了常数个临时变量
八、其他解法
解法一:直接位置映射法
python
class Solution:
def rotate(self, matrix: List[List[int]]) -> None:
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
解法二:使用额外空间(不符合要求但易于理解)
python
class Solution:
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[j][n-1-i] = matrix[i][j]
# 复制回原矩阵
for i in range(n):
for j in range(n):
matrix[i][j] = rotated[i][j]
九、常见问题与解答
Q1: 如果我想逆时针旋转90度怎么办?
A1: 有两种方法:
-
先转置,再垂直翻转(而不是水平翻转)
-
先水平翻转,再转置
Q2: 这个方法适用于非方阵吗?
A2: 不适用。旋转90度的定义只对方阵有意义。对于非方阵,旋转后行列数会变化,需要新的矩阵。
Q3: 如果矩阵中有负数或0怎么办?
A3: 算法与元素值无关,只与位置有关。无论元素是什么值,算法都能正确旋转。
Q4: 如何测试旋转的正确性?
A4: 可以测试以下情况:
-
1×1 矩阵
-
2×2 矩阵
-
3×3 矩阵(奇数)
-
4×4 矩阵(偶数)
-
包含各种值的矩阵
-
旋转两次应得到旋转180度的结果
-
旋转四次应回到原矩阵
Q5: 为什么不能使用额外矩阵?
A5: 题目要求原地旋转,这是对空间复杂度的要求。使用额外矩阵虽然简单,但空间复杂度为 O(n²)。
十、相关题目
1. LeetCode 1886. 判断矩阵经轮转后是否一致
python
class Solution:
def findRotation(self, mat: List[List[int]], target: List[List[int]]) -> bool:
n = len(mat)
# 四种可能的旋转角度:0, 90, 180, 270度
for _ in range(4):
if mat == target:
return True
# 旋转90度
mat = [list(row) for row in zip(*mat[::-1])]
return False
2. LeetCode 867. 转置矩阵
python
class Solution:
def transpose(self, matrix: List[List[int]]) -> List[List[int]]:
m, n = len(matrix), len(matrix[0])
# 创建转置矩阵
transposed = [[0] * m for _ in range(n)]
# 填充转置矩阵
for i in range(m):
for j in range(n):
transposed[j][i] = matrix[i][j]
return transposed
十一、总结
核心要点
-
数学原理:旋转90度 = 转置 + 水平翻转
-
原地操作:通过交换元素实现,不使用额外空间
-
边界处理:注意遍历范围和交换条件
算法步骤(转置 + 翻转法)
-
转置矩阵(行列互换),只遍历对角线下方元素
-
水平翻转每一行(每行反转)
时间复杂度与空间复杂度
-
时间复杂度:O(n²),每个元素被访问一次
-
空间复杂度:O(1),只使用常数个临时变量
适用场景
-
图像处理中的旋转操作
-
矩阵变换相关问题
-
需要原地操作的数组/矩阵问题
扩展思考
矩阵旋转问题展示了如何通过基本操作(转置、翻转)组合实现复杂变换。这种思想可以应用于:
-
其他角度的旋转(180度、270度)
-
矩阵的对称变换
-
图像处理中的各种几何变换
-
线性代数中的基变换
掌握矩阵旋转的解法不仅能够解决这类特定问题,还能加深对矩阵操作和空间复杂度的理解,是面试中常见的算法题目。