【力扣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 == matrix[i].length
  • 1 <= n <= 20
  • -1000 <= matrix[i][j] <= 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 遍历所有列

交换规则: matrix[i][j] 与 matrix[n-i-1][j] 交换

i < n/2 的原因:

  • n=4:i=0,1 遍历,交换 matrix[0]↔matrix[3],matrix[1]↔matrix[2]
  • n=5:i=0,1,2 遍历,交换 matrix[0]↔matrix[4],matrix[1]↔matrix[3],matrix[2]↔matrix[2](自身)

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,只遍历上三角(不包括主对角线)

交换规则: matrix[i][j] 与 matrix[j][i] 交换

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


六、复杂度分析

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

七、面试追问

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

八、相关题目

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

相关推荐
样例过了就是过了1 小时前
LeetCode热题100 颜色分类
c++·算法·leetcode
ZPC82101 小时前
C++ 跨平台 UDP 收发测试程序
c++·算法·机器人
ym_xixi1 小时前
《类和对象》—— 构造函数与析构函数总结
前端·c++·算法
洛水水1 小时前
【力扣100题】19. 排序链表 | 归并排序详解
算法·leetcode·链表
洛水水1 小时前
【力扣100题】21. LRU 缓存
spring·leetcode·缓存
凯瑟琳.奥古斯特1 小时前
丑数II C++三指针解法(力扣264)
数据结构·c++·算法·leetcode·职场和发展
j_xxx404_1 小时前
力扣算法:用栈消消乐,巧解相邻重复与退格字符串
c++·算法·leetcode
Hello.Reader1 小时前
算法基础(十一)—— 递归树如何看懂分治算法的运行时间
java·算法·排序算法
郝学胜-神的一滴1 小时前
二叉树与递归:解锁高级数据结构的编程内功心法
开发语言·数据结构·c++·算法·面试