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),四个相关位置是:

  1. (i, j)
  2. (j, n-1-i)
  3. (n-1-i, n-1-j)
  4. (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):

  • 遍历行 i0n/2 - 1
  • 遍历列 jin-1-i-1(即内层每行少两个元素)

对于 \(n\) 为奇数(如 3×3):

  • 中心点 (n/2, n/2) 不需要交换,所以遍历行 i0n/2 - 1(整数除法),列范围同上。

实际上可以统一为:

for i in range(0, n // 2):
    for j in range(i, n - 1 - i):
        交换四元组

6. 算法步骤

  1. n = len(matrix)
  2. 对于 i0n//2 - 1
  3. 对于 `j` 从 `i` 到 `n-2-i`(即 `n-1-i` 的前一个):
    
  4.     临时存储 `temp = matrix[i][j]`
    
  5.     `matrix[i][j] = matrix[n-1-j][i]`
    
  6.     `matrix[n-1-j][i] = matrix[n-1-i][n-1-j]`
    
  7.     `matrix[n-1-i][n-1-j] = matrix[j][n-1-i]`
    
  8.     `matrix[j][n-1-i] = temp`
    

7. 举例说明(3×3)

初始:

1 2 3
4 5 6
7 8 9

i=0, j=0n-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. 再将每一行反转

步骤:
原始:

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)\)

这样循序渐进地讲解,你理解了吗?

好的,我们这次来详细讲解 LeetCode 第 48 题「旋转图像」 。 1. 题目描述 给定一个 \( n \times n \) 的二维矩阵 matrix 表示一个图像,请你将图像 顺时针旋转 90 度 。 要求: 必须在 原地 旋转图像,这意味着你需要直接修改输入的二维矩阵。 不要使用另一个矩阵来旋转。 示例 1: 示例 2: 约束条件: \( 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 矩阵为例: 原始矩阵: 顺时针旋转 90° 后: 观察对应关系: 原来 (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 (整数除法),列范围同上。 实际上可以统一为: 6. 算法步骤 设 n = len(matrix) 对于 i 从 0 到 n//2 - 1 : 7. 举例说明(3×3) 初始: 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 的位置。 结果: 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 结果: 已经完成。 8. 代码实现(Python) 9. 另一种思路(转置 + 左右翻转) 顺时针旋转 90° 等价于: 先转置矩阵(行列互换) 再将每一行反转 步骤: 原始: 转置(行变列): 每行反转: 代码更简洁: 10. 总结 方法一(四位置循环交换)是原地操作,理解坐标映射是关键。 方法二(转置+反转)更直观易懂,同样满足原地修改(因为转置和反转都可原地)。 两种方法时间复杂度都是 \(O(n^2)\),空间复杂度 \(O(1)\)。 这样循序渐进地讲解,你理解了吗?