1 题目
给定一个 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].length1 <= n <= 20-1000 <= matrix[i][j] <= 1000
2 代码实现
c++
cpp
class Solution {
public:
void rotate(vector<vector<int>>& matrix) {
int n = matrix.size() ;
for (int i = 0 ; i < n /2 ; i ++){
swap(matrix[i] , matrix[ n - 1 - i ]);
}
for (int i = 0 ; i < n ; i ++){
for (int j = i + 1 ; j < n ; j ++){
swap(matrix[i][j],matrix[j][i]);
}
}
}
};
js
javascript
/**
* @param {number[][]} matrix
* @return {void} Do not return anything, modify matrix in-place instead.
*/
var rotate = function(matrix) {
const n = matrix.length ;
for (let i = 0 ; i < n / 2 ; i ++){
[matrix[i] , matrix[ n - 1 - i ]] = [matrix[n - 1 - i ] , matrix[i]];
}
for (let i = 0 ; i < n ; i ++){
for (let j = i + 1 ; j < n ; j ++){
[matrix[i][j] , matrix[j][i] ] = [matrix[j][i] , matrix[i][j]];
}
}
};
注意 JS 版的 swap(a, b),ES6 解构赋值。
javascript
// 交换 a 和 b
[a, b] = [b, a];
思考
不能用新的数组,不知道怎么从空间上抽象出来,感觉好难。。啊啊!!
就是先上下翻转,然后再左右沿着主对角线翻转!!
题解
先看懂:顺时针旋转 90° 是什么效果?
拿 3×3 矩阵举例:
原矩阵: 旋转后:
1 2 3 7 4 1
4 5 6 → 8 5 2
7 8 9 9 6 3
核心规律:顺时针旋转 90° = 先上下翻转 + 再对角线翻转这是原地旋转最简单的思路,不用记复杂坐标公式!
解题步骤(死记这两步就能做)
步骤 1:矩阵 上下翻转
把第一行和最后一行交换,第二行和倒数第二行交换......3×3 例子:
1 2 3 7 8 9
4 5 6 → 4 5 6
7 8 9 1 2 3
步骤 2:矩阵 沿主对角线翻转(左上→右下)
主对角线就是 (0,0)→(1,1)→(2,2) 这条线,交换对称的元素:
7 8 9 7 4 1
4 5 6 → 8 5 2
1 2 3 9 6 3
完整代码
cpp
class Solution {
public:
void rotate(vector<vector<int>>& matrix) {
int n = matrix.size(); // 获取矩阵边长 n×n
// 第一步:上下翻转矩阵
for (int i = 0; i < n / 2; i++) {
// 第 i 行 和 第 n-1-i 行 交换
swap(matrix[i], matrix[n - 1 - i]);
}
// 第二步:沿主对角线翻转(左上到右下)
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
// 交换 matrix[i][j] 和 matrix[j][i]
swap(matrix[i][j], matrix[j][i]);
}
}
}
};
代码逐行讲解
1. 获取矩阵边长
int n = matrix.size();因为是 n×n 正方形矩阵,行数 = 列数,直接用行数即可。
2. 上下翻转
cpp
for (int i = 0; i < n / 2; i++) {
swap(matrix[i], matrix[n - 1 - i]);
}
- 只需要循环
n/2次(翻完前一半,后一半就对应好了) swap(matrix[i], matrix[n-1-i]):直接交换两行,C++ 自带函数,超方便
3. 对角线翻转
cpp
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
swap(matrix[i][j], matrix[j][i]);
}
}
- 只遍历对角线右上部分 (
j = i+1),避免重复交换 - 交换
(i,j)和(j,i)两个位置的元素
测试示例(验证代码)
示例 1 输入:
matrix = [[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]]✅
示例 2 输入:
matrix = [[5,1,9,11],[2,4,8,10],[13,3,6,7],[15,14,12,16]]
- 上下翻转 → 第一行和第四行交换,第二行和第三行交换
- 对角线翻转 → 直接得到题目答案 ✅
- 原地旋转 90° 最简方法:上下翻转 + 对角线翻转
- 代码只有两个循环,时间复杂度 O (n²)(必须遍历所有元素)
- 空间复杂度 O (1)(完全原地修改,符合题目要求)
总结
- 解题核心:顺时针旋转 90° = 上下翻转 + 主对角线翻转,这是最适合新手的原地解法
- 代码无额外数组,纯原地修改,满足题目要求
- 两个简单循环就能实现,逻辑清晰、好记好写
3 题目
给定一个 mxn 的矩阵,如果一个元素为 0 ,则将其所在行和列的所有元素都设为 0 。请使用 原地 算法**。**
示例 1:

输入:matrix = [[1,1,1],[1,0,1],[1,1,1]]
输出:[[1,0,1],[0,0,0],[1,0,1]]
示例 2:

输入:matrix = [[0,1,2,0],[3,4,5,2],[1,3,1,5]]
输出:[[0,0,0,0],[0,4,5,0],[0,3,1,0]]
提示:
m == matrix.lengthn == matrix[0].length1 <= m, n <= 200-231 <= matrix[i][j] <= 231 - 1
进阶:
- 一个直观的解决方案是使用
O(m n)的额外空间,但这并不是一个好的解决方案。 - 一个简单的改进方案是使用
O(m+n)的额外空间,但这仍然不是最好的解决方案。 - 你能想出一个仅使用常量空间的解决方案吗?
4 代码实现
c++
cpp
class Solution {
public:
void setZeroes(vector<vector<int>>& matrix) {
int m = matrix.size();
int n = matrix[0].size();
bool row0 = false ;
bool col0 = false ;
for (int i = 0 ; i < n ; i ++){
if (matrix[0][i] == 0 ){
row0 = true ;
break ;
}
}
for (int i = 0 ; i < m ; i ++){
if (matrix[i][0] == 0 ){
col0 = true ;
break ;
}
}
for (int i = 1 ; i < m ; i++){
for (int j = 1 ; j < n ; j ++){
if (matrix[i][j] == 0){
matrix[i][0] = 0 ;
matrix[0][j] = 0 ;
}
}
}
for (int i = 1 ; i < m ; i ++){
if(matrix[i][0] == 0 ){
for (int j = 0 ; j < n ; j ++){
matrix[i][j] = 0 ;
}
}
}
for (int j = 1 ; j < n ; j ++){
if (matrix[0][j] == 0 ){
for (int i = 0 ; i < m ; i++){
matrix[i][j] = 0 ;
}
}
}
if (row0){
for (int j = 0 ; j < n ; j ++){
matrix[0][j] = 0 ;
}
}
if (col0){
for (int i = 0 ; i < m ; i ++){
matrix[i][0] = 0 ;
}
}
}
};
js
javascript
var setZeroes = function(matrix) {
const m = matrix.length; // 行数
const n = matrix[0].length; // 列数
// 标记:第一行、第一列原来有没有 0
let firstRowZero = false;
let firstColZero = false;
// 1. 检查第一行有没有 0
for (let j = 0; j < n; j++) {
if (matrix[0][j] === 0) {
firstRowZero = true;
break;
}
}
// 2. 检查第一列有没有 0
for (let i = 0; i < m; i++) {
if (matrix[i][0] === 0) {
firstColZero = true;
break;
}
}
// 3. 遍历中间区域,打标记
// matrix[i][0] = 行标记
// matrix[0][j] = 列标记
for (let i = 1; i < m; i++) {
for (let j = 1; j < n; j++) {
if (matrix[i][j] === 0) {
matrix[i][0] = 0; // 标记第 i 行要置 0
matrix[0][j] = 0; // 标记第 j 列要置 0
}
}
}
// 4. 根据行标记置 0
for (let i = 1; i < m; i++) {
if (matrix[i][0] === 0) {
for (let j = 0; j < n; j++) {
matrix[i][j] = 0;
}
}
}
// 5. 根据列标记置 0
for (let j = 1; j < n; j++) {
if (matrix[0][j] === 0) {
for (let i = 0; i < m; i++) {
matrix[i][j] = 0;
}
}
}
// 6. 最后处理第一行
if (firstRowZero) {
for (let j = 0; j < n; j++) {
matrix[0][j] = 0;
}
}
// 7. 最后处理第一列
if (firstColZero) {
for (let i = 0; i < m; i++) {
matrix[i][0] = 0;
}
}
};
思考
脑袋里比较直接的想法就是出现了一个上下左右的,呃,有点像之前那个烂掉的橘子背的模板,但是我不记得了具体的代码怎么写!!!bfs吗?
看了题解以后,应该是最直观的解法了,第一行第一列作为标记(下面称为标记数组),现在的情况是有0则行列都为0,啥意思呢,现在这两个标记数组,如果遇到0了,对应的就是标记数组里面的bool值转变,于是到最后遍历完了以后,再结算,对应的行列置0。
那为什么要单独记录 firstRowZero、firstColZero?
因为:第一行、第一列自己也可能原本就有 0但我们把它们当成标记用了,会被覆盖!
所以必须提前保存:
- 第一行原来有没有 0?
- 第一列原来有没有 0?
最后再把它们自己置 0。
题解
题目要求
- 矩阵中任何一个位置是 0
- 它整行 + 整列全部变 0
- 必须 原地 O (1) 额外空间(不能开新数组)
核心思路(最简单、最标准)
- 不能边遍历边改 0,会把原始 0 覆盖
- 用 第一行、第一列 当标记位
matrix[i][0] = 0→ 第 i 行要置 0matrix[0][j] = 0→ 第 j 列要置 0
- 先记录:第一行、第一列原本有没有 0
- 最后统一置 0
完整 C++ 代码
cpp
class Solution {
public:
void setZeroes(vector<vector<int>>& matrix) {
int m = matrix.size();
int n = matrix[0].size();
// 标记第一行、第一列原来有没有0
bool firstRowZero = false;
bool firstColZero = false;
// 1. 检查第一行有没有0
for (int j = 0; j < n; j++) {
if (matrix[0][j] == 0) {
firstRowZero = true;
break;
}
}
// 2. 检查第一列有没有0
for (int i = 0; i < m; i++) {
if (matrix[i][0] == 0) {
firstColZero = true;
break;
}
}
// 3. 遍历剩下的矩阵,用第一行、第一列做标记
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
if (matrix[i][j] == 0) {
matrix[i][0] = 0; // 标记第i行要置0
matrix[0][j] = 0; // 标记第j列要置0
}
}
}
// 4. 根据标记置零 行
for (int i = 1; i < m; i++) {
if (matrix[i][0] == 0) {
for (int j = 0; j < n; j++) {
matrix[i][j] = 0;
}
}
}
// 5. 根据标记置零 列
for (int j = 1; j < n; j++) {
if (matrix[0][j] == 0) {
for (int i = 0; i < m; i++) {
matrix[i][j] = 0;
}
}
}
// 6. 最后置零第一行、第一列
if (firstRowZero) {
for (int j = 0; j < n; j++) {
matrix[0][j] = 0;
}
}
if (firstColZero) {
for (int i = 0; i < m; i++) {
matrix[i][0] = 0;
}
}
}
};
超简短步骤总结
- 记录第一行、第一列原本有没有 0
- 遍历矩阵,把 行标记放第一列,列标记放第一行
- 用标记把中间区域置 0
- 最后处理第一行、第一列
为什么这题不是 BFS / 腐烂橘子?
- 橘子:4 方向扩散,一层一层
- 本题:只要一个 0 → 整行整列直接清空
- 完全不同逻辑!
5 小结
题一拿来就不会做,一看就废。
题解拿到了一下就觉得很简单,思路也不过如此,但是目前很重要的,没有思路,思路有了没有具体的代码写法。
加油吧,多练多练。