🔄 题目链接: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]
这个映射关系是本题的核心突破口!
🔍 核心算法及代码讲解
✅ 方法一:使用辅助数组(非原地)
这是最直观的做法,但不符合“原地”要求。
🧩 思路:
- 创建一个新的二维数组
matrix_new,大小与原矩阵相同。 - 遍历原矩阵每个元素
matrix[i][j],将其放到新矩阵的对应位置matrix_new[j][n-i-1]。 - 最后把
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° = 先水平翻转 + 再主对角线翻转
步骤分解:
- 水平翻转(上下颠倒):
swap(matrix[i][j], matrix[n-i-1][j]); - 主对角线翻转(转置):
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) 时间复杂度。
🔹 考点:二分查找、双指针、矩阵遍历、边界判断。
🔹 难度:中等,但考察思维灵活性,是很多“有序矩阵”问题的经典模型。
🔹 提示:不要暴力遍历!利用单调性剪枝!
💡 提示:从右上角出发,如果当前值大于目标,向左移;小于目标,向下移。这样能高效缩小搜索空间!
📌 记住:当你在刷题时,不要只看答案,要像写这篇文章一样,深入思考每一步背后的原理、优化空间和面试价值。这才是真正提升算法能力的方式!