【LeetCode Hot100 刷题日记(20/100)】48.旋转图像——原地旋转 、翻转模拟、数组、矩阵、原地操作、数学🔄

22 阅读8分钟

🔄 题目链接:leetcode.cn/problems/rotate-image
🔍 难度:中等 | 🏷️ 标签:数组、矩阵、原地操作、数学
⏱️ 目标时间复杂度:O(n²)
💾 空间复杂度:O(1)(最优解)


🧠 题目分析

给定一个 n × n 的二维矩阵 matrix,表示一个图像。要求将该图像顺时针旋转 90 度,并且必须在 原地 完成操作(即不能使用额外的矩阵存储结果)。

✅ 关键约束:

  • 必须原地修改输入矩阵。
  • 不能使用额外的二维数组来保存结果。
  • 时间复杂度尽量优化到 O(n²),空间复杂度为 O(1)。

📊 示例回顾:

输入:[[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]]

观察发现:

  • i 行第 j 列的元素 → 旋转后位于第 j 列第 n-i-1 行。
  • 即:matrix[i][j] → matrix[j][n-i-1]

这个映射关系是本题的核心突破口!


🔍 核心算法及代码讲解

✅ 方法一:使用辅助数组(非原地)

这是最直观的做法,但不符合“原地”要求。

🧩 思路:

  1. 创建一个新的二维数组 matrix_new,大小与原矩阵相同。
  2. 遍历原矩阵每个元素 matrix[i][j],将其放到新矩阵的对应位置 matrix_new[j][n-i-1]
  3. 最后把 matrix_new 赋值回原矩阵。

❌ 缺点:

  • 空间复杂度 O(n²),不满足“原地”要求。
  • 不适合面试中追求极致空间优化的场景。
// 方法一:辅助数组(非原地)
void rotate(vector<vector<int>>& matrix) {
    int n = matrix.size();
    auto matrix_new = matrix; // 值拷贝,创建副本
    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < n; ++j) {
            matrix_new[j][n - i - 1] = matrix[i][j]; // 映射规则
        }
    }
    matrix = matrix_new; // 复制回原矩阵
}

⚠️ 注意:虽然 C++ 中 auto matrix_new = matrix 是值拷贝,但在大矩阵下会占用大量内存,面试官可能会追问:“有没有更优方式?”


✅ 方法二:原地旋转(四元环交换)

这才是真正意义上的“原地旋转”,也是面试中最常考察的解法。

🧩 核心思想:

  • 每次旋转涉及 四个元素 形成一个循环:
    A -> B -> C -> D -> A
    
  • 使用一个临时变量 temp 来完成这四个位置的交换。
  • 只需遍历左上角的一个子矩阵即可覆盖所有元素,避免重复或遗漏。

🔁 四个位置的映射关系:

设当前处理的是 (i, j),则其旋转后的四个位置为:

位置坐标
A(i, j)
B(j, n-i-1)
C(n-i-1, n-j-1)
D(n-j-1, i)

它们构成一个闭合循环,可以一次交换完成。

🧩 枚举范围:

  • n 为偶数时,只需遍历前 n/2 行和前 n/2 列。
  • n 为奇数时,中心点不动,只需遍历前 n/2 行和前 (n+1)/2 列。

✅ 所以外层循环:i ∈ [0, n/2)
✅ 内层循环:j ∈ [0, (n+1)/2)
这样保证了不重复、不遗漏。

💡 为什么是 (n+1)/2

因为当 n 为奇数时,中间列也需要参与旋转(如 5x5 矩阵的第 2 列),而 (n+1)/2 能正确处理奇偶情况。

// 方法二:原地旋转(四元环交换)
void rotate(vector<vector<int>>& matrix) {
    int n = matrix.size();
    for (int i = 0; i < n / 2; ++i) {           // 控制行
        for (int j = 0; j < (n + 1) / 2; ++j) { // 控制列
            int temp = matrix[i][j];             // 保存原始值
            matrix[i][j] = matrix[n - j - 1][i]; // A ← D
            matrix[n - j - 1][i] = matrix[n - i - 1][n - j - 1]; // B ← C
            matrix[n - i - 1][n - j - 1] = matrix[j][n - i - 1]; // C ← B
            matrix[j][n - i - 1] = temp;         // D ← A
        }
    }
}

行注释说明

  • temp = matrix[i][j]:保存起始点值,防止被覆盖。
  • matrix[i][j] = matrix[n-j-1][i]:将右上角的值移到左上角。
  • 后续三步依次传递,形成完整循环。
  • 每轮只处理一个“环”,确保无重复。

✅ 方法三:用翻转代替旋转(经典技巧)

这是一种非常巧妙的思路,利用两次翻转实现旋转效果。

🧩 核心原理:

顺时针旋转 90° = 先水平翻转 + 再主对角线翻转

步骤分解:
  1. 水平翻转(上下颠倒):
    swap(matrix[i][j], matrix[n-i-1][j]);
    
  2. 主对角线翻转(转置):
    swap(matrix[i][j], matrix[j][i]);  // 仅遍历 i > j 的部分
    

🧠 为什么有效?

我们来验证一下变换链:

原位置 (i,j)
→ 水平翻转后:(n-i-1, j)
→ 主对角线翻转后:(j, n-i-1)

正是我们想要的旋转目标!

✅ 优势:

  • 更容易理解和记忆。
  • 代码简洁,逻辑清晰。
  • 是面试中展示思维灵活性的好方法。
// 方法三:翻转法(推荐用于面试表达)
void rotate(vector<vector<int>>& matrix) {
    int n = matrix.size();
    
    // 1. 水平翻转:上下对调
    for (int i = 0; i < n / 2; ++i) {
        for (int j = 0; j < n; ++j) {
            swap(matrix[i][j], matrix[n - i - 1][j]);
        }
    }
    
    // 2. 主对角线翻转:转置
    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < i; ++j) { // 只遍历上三角
            swap(matrix[i][j], matrix[j][i]);
        }
    }
}

注意:第二层循环 j < i 是为了避免重复交换,且只处理对角线下方区域。


🧩 解题思路(分步详解)

📌 步骤 1:理解旋转的本质

  • 顺时针旋转 90°:每行变成对应列的反向排列。
  • 数学公式:(i,j) → (j, n-i-1)
  • 每个元素移动到新的位置,不能直接赋值,否则会丢失数据。

📌 步骤 2:选择策略

  • 若允许额外空间 → 使用辅助数组。
  • 若要求原地 → 选 四元环交换翻转法

📌 步骤 3:确定枚举范围

  • 避免重复:只遍历左上象限。
  • 中心点无需处理(奇数阶)。
  • 使用 n/2(n+1)/2 统一处理奇偶。

📌 步骤 4:实现循环交换

  • 使用 temp 存储第一个值。
  • 依次将后续三个值向前推进。
  • 最后将 temp 放入最后一个位置。

📌 步骤 5:验证边界

  • 测试 n=1, n=2, n=3, n=4 等小规模案例。
  • 特别关注奇偶性差异。

📊 算法分析

方法时间复杂度空间复杂度是否原地适用场景
辅助数组O(n²)O(n²)初学者理解用
四元环交换O(n²)O(1)面试首选
翻转法O(n²)O(1)展示思维深度

面试建议

  • 推荐先讲 翻转法,因为它更易理解且有通用性。
  • 再补充 四元环交换,体现对细节的掌控力。
  • 如果时间充足,可对比两种方法的优劣。

💻 代码(完整模板)

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

// 方法二:原地旋转(四元环交换)
void rotate(vector<vector<int>>& matrix) {
    int n = matrix.size();
    for (int i = 0; i < n / 2; ++i) {
        for (int j = 0; j < (n + 1) / 2; ++j) {
            int temp = matrix[i][j];
            matrix[i][j] = matrix[n - j - 1][i];
            matrix[n - j - 1][i] = matrix[n - i - 1][n - j - 1];
            matrix[n - i - 1][n - j - 1] = matrix[j][n - i - 1];
            matrix[j][n - i - 1] = temp;
        }
    }
}

// 方法三:翻转法(推荐)
void rotate_v2(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]);
        }
    }
}

// 测试
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);

    // 测试用例 1
    vector<vector<int>> mat1 = {{1,2,3},{4,5,6},{7,8,9}};
    cout << "Before: " << endl;
    for (auto& row : mat1) {
        for (int x : row) cout << x << " ";
        cout << endl;
    }
    rotate(mat1);
    cout << "After: " << endl;
    for (auto& row : mat1) {
        for (int x : row) cout << x << " ";
        cout << endl;
    }

    // 测试用例 2
    vector<vector<int>> mat2 = {{5,1,9,11},{2,4,8,10},{13,3,6,7},{15,14,12,16}};
    cout << "\nBefore: " << endl;
    for (auto& row : mat2) {
        for (int x : row) cout << x << " ";
        cout << endl;
    }
    rotate_v2(mat2);
    cout << "After: " << endl;
    for (auto& row : mat2) {
        for (int x : row) cout << x << " ";
        cout << endl;
    }

    return 0;
}

✅ 输出验证:

Before:
1 2 3
4 5 6
7 8 9
After:
7 4 1
8 5 2
9 6 3

🧠 面试拓展知识点

🔹 逆时针旋转 90° 怎么办?

  • 类似逻辑,只是方向相反。
  • 逆时针 90° = 先主对角线翻转 + 再水平翻转
  • 或者直接改映射关系:(i,j) → (n-j-1, i)

🔹 旋转任意角度?

  • 一般需要使用坐标变换或插值算法(超出本题范畴)。
  • 但了解基础旋转是后续图像处理的基础。

🔹 如何推广到 m×n 矩阵?

  • 本题限定为正方形,若为矩形,则无法“原地”旋转。
  • 需要额外空间或重新定义目标布局。

🔹 实际应用?

  • 图像处理中的旋转功能。
  • 游戏开发中地图翻转。
  • 数据结构中矩阵操作优化。

🌟 本期完结,下期见!🔥

👉 点赞收藏加关注,新文更新不迷路。关注专栏【算法】LeetCode Hot100刷题日记,持续为你拆解每一道热题的底层逻辑与面试技巧!

💬 欢迎留言交流你的解法或疑问!一起进步,冲向 Offer!💪


📣 下一期预告:LeetCode 热题 100 第24题 —— 搜索二维矩阵 II(中等)

🔹 题目:编写一个高效的算法来搜索 m x n 矩阵中的目标值。矩阵具有以下特性:

  • 每行从左到右递增。
  • 每列从上到下递增。

🔹 核心思路:从右上角或左下角开始,每次排除一行或一列,实现 O(m+n) 时间复杂度。

🔹 考点:二分查找、双指针、矩阵遍历、边界判断。

🔹 难度:中等,但考察思维灵活性,是很多“有序矩阵”问题的经典模型。

🔹 提示:不要暴力遍历!利用单调性剪枝!

💡 提示:从右上角出发,如果当前值大于目标,向左移;小于目标,向下移。这样能高效缩小搜索空间!


📌 记住:当你在刷题时,不要只看答案,要像写这篇文章一样,深入思考每一步背后的原理、优化空间和面试价值。这才是真正提升算法能力的方式!