Leetcode 156 旋转图像 | 矩阵置零

1 题目

48. 旋转图像

给定一个 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

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]]

  1. 上下翻转 → [[7,8,9],[4,5,6],[1,2,3]]
  2. 对角线翻转 → [[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]]

  1. 上下翻转 → 第一行和第四行交换,第二行和第三行交换
  2. 对角线翻转 → 直接得到题目答案 ✅

  1. 原地旋转 90° 最简方法:上下翻转 + 对角线翻转
  2. 代码只有两个循环,时间复杂度 O (n²)(必须遍历所有元素)
  3. 空间复杂度 O (1)(完全原地修改,符合题目要求)

总结

  1. 解题核心:顺时针旋转 90° = 上下翻转 + 主对角线翻转,这是最适合新手的原地解法
  2. 代码无额外数组,纯原地修改,满足题目要求
  3. 两个简单循环就能实现,逻辑清晰、好记好写

3 题目

73. 矩阵置零

给定一个 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.length
  • n == matrix[0].length
  • 1 <= 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) 额外空间(不能开新数组)

核心思路(最简单、最标准)

  1. 不能边遍历边改 0,会把原始 0 覆盖
  2. 第一行、第一列 当标记位
    • matrix[i][0] = 0 → 第 i 行要置 0
    • matrix[0][j] = 0 → 第 j 列要置 0
  3. 先记录:第一行、第一列原本有没有 0
  4. 最后统一置 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;
            }
        }
    }
};

超简短步骤总结

  1. 记录第一行、第一列原本有没有 0
  2. 遍历矩阵,把 行标记放第一列,列标记放第一行
  3. 用标记把中间区域置 0
  4. 最后处理第一行、第一列

为什么这题不是 BFS / 腐烂橘子?

  • 橘子:4 方向扩散,一层一层
  • 本题:只要一个 0 → 整行整列直接清空
  • 完全不同逻辑!

5 小结

题一拿来就不会做,一看就废。

题解拿到了一下就觉得很简单,思路也不过如此,但是目前很重要的,没有思路,思路有了没有具体的代码写法。

加油吧,多练多练。

相关推荐
Highcharts.js2 小时前
在 Next.js App Router 中使用 Highcharts Stock(完整实战指南 )
开发语言·javascript·ecmascript
papership2 小时前
【入门级-数据结构-4、简单图:图的定义与相关概念】
数据结构·算法
计算机安禾2 小时前
【数据结构与算法】第46篇:算法思想(一):递归与分治
c语言·数据结构·c++·算法·visualstudio·图论·visual studio code
Sirens.2 小时前
七大经典排序算法:原理、实现与复杂度分析
java·数据结构·算法·排序算法
万邦科技Lafite2 小时前
通过淘宝关键词API接口批量获取商品信息指南
java·前端·javascript
jingxindeyi2 小时前
electron 配置 shadcn-ui
javascript·ui·electron
wfbcg2 小时前
每日算法练习:LeetCode 54. 螺旋矩阵 ✅
算法·leetcode·矩阵
黎阳之光2 小时前
【从虚拟到实体:黎阳之光实时三维重构,开启AI空间智能新纪元
大数据·人工智能·算法·安全·数字孪生
jghhh012 小时前
基于主从博弈的主动配电网阻塞管理:MATLAB实现
算法·matlab