【力扣100题】24. 旋转图像

一、题目描述

给定一个 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 == matrixi.length
  • 1 <= n <= 20
  • -1000 <= matrixij <= 1000

二、解题思路总览

核心思想:两次翻转代替旋转

顺时针旋转 90 度可以通过两次翻转实现:

  • 方法一:先上下翻转,再按主对角线转置
  • 方法二:先按主对角线转置,再左右翻转

旋转与翻转的数学关系:

复制代码
顺时针旋转 90 度 = 上下翻转 + 主对角线转置
顺时针旋转 90 度 = 左右翻转 + 副对角线转置

为什么可以用翻转代替旋转?

观察旋转前后的坐标映射关系:

复制代码
旋转前 (i, j) → 旋转后 (j, n-1-i)

两步翻转的合成效果:
1. 上下翻转: (i, j) → (n-1-i, j)
2. 转置:     (n-1-i, j) → (j, n-1-i)

最终结果正好是顺时针旋转 90 度!
方法 操作 时间复杂度 空间复杂度
两次翻转(本题) 上下翻转 + 转置 O(n^2) O(1)
原地旋转公式 直接用公式计算目标位置 O(n^2) O(1)
辅助矩阵 额外开一个矩阵 O(n^2) O(n^2)

三、完整代码

cpp 复制代码
class Solution {
public:
    void rotate(vector<vector<int>>& matrix) {
        int n = matrix.size();

        // 第一次翻转:上下翻转(沿着水平中线)
        for (int i = 0; i < n / 2; i++) {
            for (int j = 0; j < n; j++) {
                swap(matrix[i][j], matrix[n - i - 1][j]);
            }
        }

        // 第二次翻转:按主对角线转置
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < i; j++) {
                swap(matrix[i][j], matrix[j][i]);
            }
        }
    }
};

四、算法流程图

4.1 整体旋转流程

复制代码
输入:matrix(n x n 方阵)

[Step 1] 获取矩阵大小
         n = matrix.size()
         |
         v
[Step 2] 第一次翻转:上下翻转
         |
         v
    for i = 0 to n/2 - 1:
        for j = 0 to n - 1:
            swap(matrix[i][j], matrix[n-i-1][j])
         |
         v
[Step 3] 第二次翻转:按主对角线转置
         |
         v
    for i = 0 to n - 1:
        for j = 0 to i - 1:
            swap(matrix[i][j], matrix[j][i])
         |
         v
[Step 4] 【返回】matrix 已在原地修改

4.2 第一次翻转:上下翻转流程

复制代码
操作:以水平中线为轴,上下翻转
对应关系:matrix[i][j] ↔ matrix[n-i-1][j]

例如 n=3:
  i=0: matrix[0][j] ↔ matrix[2][j]  (第0行和第2行交换)
  i=1: matrix[1][j] ↔ matrix[1][j]  (第1行和自己交换,实际跳过)

具体执行:
  初始:[[1,2,3],
         [4,5,6],
         [7,8,9]]

  i=0, j=0,1,2:
    swap(matrix[0][0], matrix[2][0]) → swap(1, 7)
    swap(matrix[0][1], matrix[2][1]) → swap(2, 8)
    swap(matrix[0][2], matrix[2][2]) → swap(3, 9)

  结果:[[7,8,9],
         [4,5,6],
         [1,2,3]]

4.3 第二次翻转:转置流程

复制代码
操作:沿主对角线交换,即 matrix[i][j] ↔ matrix[j][i]
只遍历上三角:i > j 的区域

具体执行:
  初始(来自上一步):[[7,8,9],
                        [4,5,6],
                        [1,2,3]]

  i=0: j=0 → 跳过(i==j不需要交换)
  i=1: j=0 → swap(matrix[1][0], matrix[0][1]) → swap(4, 8) → [[7,4,9],[8,5,6],[1,2,3]]
       j=1 → 跳过
  i=2: j=0 → swap(matrix[2][0], matrix[0][2]) → swap(1, 9) → [[7,4,1],[8,5,6],[9,2,3]]
       j=1 → swap(matrix[2][1], matrix[1][2]) → swap(2, 6) → [[7,4,1],[8,5,2],[9,6,3]]
       j=2 → 跳过

  结果:[[7,4,1],
         [8,5,2],
         [9,6,3]]

4.4 完整执行过程示例

复制代码
输入:[[1,2,3],
       [4,5,6],
       [7,8,9]]

【第一步:上下翻转】
  翻转后:[[7,8,9],
           [4,5,6],
           [1,2,3]]

【第二步:按主对角线转置】
  转置后:[[7,4,1],
           [8,5,2],
           [9,6,3]]

输出:[[7,4,1],[8,5,2],[9,6,3]]
验证:正确!

4.5 翻转顺序验证(先转置后左右翻转)

复制代码
输入:[[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]]

输出:[[7,4,1],[8,5,2],[9,6,3]]
验证:结果相同!

五、逐行解析

5.1 第一次翻转:上下翻转

cpp 复制代码
for (int i = 0; i < n / 2; i++) {
    for (int j = 0; j < n; j++) {
        swap(matrix[i][j], matrix[n - i - 1][j]);
    }
}

外层循环: i 从 0 到 n/2-1,只遍历上半部分(不包括中线,中线不需要翻转)

内层循环: j 遍历所有列

交换规则: matrixij 与 matrixn-i-1j 交换

i < n/2 的原因:

  • n=4:i=0,1 遍历,交换 matrix0↔matrix3,matrix1↔matrix2
  • n=5:i=0,1,2 遍历,交换 matrix0↔matrix4,matrix1↔matrix3,matrix2↔matrix2(自身)

5.2 第二次翻转:转置

cpp 复制代码
for (int i = 0; i < n; i++) {
    for (int j = 0; j < i; j++) {
        swap(matrix[i][j], matrix[j][i]);
    }
}

外层循环: i 从 0 到 n-1,遍历所有行

内层循环: j 从 0 到 i-1,只遍历上三角(不包括主对角线)

交换规则: matrixij 与 matrixji 交换

j < i 的原因: 只处理主对角线以上的元素,主对角线自身不需要交换


六、复杂度分析

指标 复杂度 说明
时间复杂度 O(n^2) 两次遍历,每次 O(n^2)
空间复杂度 O(1) 只用了常数个变量,原地修改

七、面试追问

问题 回答要点
为什么要用两次翻转而不是直接旋转? 直接旋转需要保存一个元素然后用公式移动,代码复杂且容易出错;两次翻转直观易懂
为什么先上下翻转再转置可以等价于旋转? 数学推导:旋转 (i,j)→(j,n-1-i),翻转合成效果相同
为什么不先转置再上下翻转? 也可以,两种顺序等价,都能得到正确结果
n/2 的意义是什么? 只遍历上半部分,因为下半部分会和上半部分交换,遍历完整矩阵会换回来
转置为什么只遍历 j < i 的区域? 转置时 matrixij 和 matrixji 是配对交换,遍历上三角即可避免重复
能否只用一个循环完成? 可以用公式直接计算每个元素的目标位置,但代码更复杂
如果 n 是奇数会有问题吗? 不会,中间的行(i = n/2)上下翻转时自己和自己交换,不需要特殊处理
如何验证旋转的正确性? 旋转前 (i, j) 的元素,旋转后应该在 (j, n-1-i) 的位置

八、相关题目

题号 题目 关键点
48 旋转图像 本题
54 螺旋矩阵 矩阵遍历
59 螺旋矩阵 II 矩阵生成
73 矩阵置零 原地标记

相关推荐
YH55269845 小时前
GPT‑5.6 Sol 原本支持 1M 上下文,Codex 现已放开此前限制,如何看待这次调整?
java·jvm·人工智能·gpt·算法·chatgpt
遥感知识服务6 小时前
从局部阈值、双极化到暗地表剔除:NASA OPERA DSWx-S1全球动态水体算法拆解
大数据·人工智能·深度学习·神经网络·算法·机器学习
晚风醉蝶6 小时前
1-13-TimSort
算法
ZJU_统一阿萨姆7 小时前
【算子开发】算子融合与Softmax_LayerNorm实现
人工智能·算法·语言模型·硬件架构
叩码以求索9 小时前
浅谈:前序遍历反转法求解N叉树的后序遍历
算法
北风toto10 小时前
中缀、前缀、后缀表达式
算法·软件设计师
听取WA声一片(无恶意)10 小时前
CSP-J/CSP-S 深度优先搜索(DFS)完全讲义
c++·算法·深度优先
码流怪侠10 小时前
2026年8月GitHub热榜深度拆解:Agent Skills席卷开源圈,一个“技能包“收割5万星
算法·程序员·github
hold?fish:palm10 小时前
30 两两交换链表中的节点
数据结构·算法·链表
Nil20811 小时前
leetcode 98验证二叉搜索树
算法·leetcode·职场和发展