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].length
  • 1 <= 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 度。

笨办法: 拿一张新的空白照片纸,把每个像素复制到新位置。但这需要额外一张纸。

聪明办法(两步法):

  1. 沿对角线翻折(转置):就像把书页沿左上到右下的中线对折——(i,j)(j,i) 交换。
  2. 沿垂直中线左右翻折(每行反转):就像合上书本一样,把每一行左右颠倒。

经过这两步,照片就神奇地旋转了 90 度!

生活中的类比:

  • 你有一个装满数字的方盒子,想把它侧过来看
  • 就像做魔方的一个面旋转
  • 就像在纸上写字后把纸转 90 度阅读

解题思路

思路一:转置 + 翻转(推荐)

核心洞察:

顺时针旋转 90 度可以拆解为两个简单步骤:

  1. 转置:沿主对角线(左上到右下)翻转,即 matrix[i][j]matrix[j][i] 交换
  2. 翻转:每行逆序(左右翻转),即 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]

易错点

  • 转置的遍历范围ji+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)上下翻转 + 左右翻转

关联题目

  • 54-螺旋矩阵 — 矩阵遍历问题,同样涉及边界控制和层次操作。旋转和螺旋遍历是矩阵操作的两大经典题型。
  • 73-矩阵置零 — 矩阵原地操作问题,同样利用了首行首列作为标记。与本题的转置+翻转思路不同,但都属于”在矩阵上做变换”的范畴。
  • 867-转置矩阵 — 本题的第一步(转置)就是这道题。但 867 不要求方阵,对于非方阵转置需要创建新矩阵。
  • 189-轮转数组 — 一维数组的轮转,与本题思路相通:一维用三次翻转,二维先用转置再翻转。