Leecode热题100:矩阵置零(矩阵)

给定一个 m x n 的矩阵,如果一个元素为 0 ,则将其所在行和列的所有元素都设为 0 。请使用 原地 算法。

示例:

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

(来源:Leecode)


第一步:拆解问题的本质

我们要解决的"物理事实"是什么?

  1. 输入:一个 的数值网格。

  2. 规则:如果某个点 是 0,那么整个第 行和第 列都要变成 0。

  3. 约束:必须是原地(In-place) 。这意味着我们不能简单地通过复制一个完整的矩阵来解决问题(那会消耗 的空间)。

核心矛盾:

如果我们直接遍历并修改矩阵,我们会污染数据

  • 比如: 原本是 1,但因为它所在的第 0 行有另一个 0,它被改成了 0。

  • 当我们随后遍历到 时,我们会误以为它原本就是 0,从而错误地把第 0 列也清零。

推导出的基础真理:

我们需要一种方式来记录哪些行、哪些列需要清零,且这种记录不能干扰我们对"原始 0"的判断。


第二步:寻找存储空间的"最小化"

既然需要记录,记录在哪?

  • 方案 A(直觉推导) :创建一个 的布尔矩阵记录 0 的位置。

    • 评价:违背了原地算法的要求,空间复杂度太高。
  • 方案 B(进一步压缩) :我真的需要记录每一个点吗?不需要。我只需要知道"第 行是否要清零"和"第 列是否要清零"。

    • 物理事实 :我们需要 个标记位(行)和 个标记位(列)。

    • 空间复杂度

第一性原理的终极拷问:

如果没有额外的 空间呢?既然矩阵本身就在内存里,我们能不能借用矩阵内部的空间来做标记?


第三步:利用矩阵自身作为账本

如果我们用矩阵的第一行第一列 来充当那 的标记位:

  • 如果,我们就把 设为 0(代表第 行要清零),把 设为 0(代表第 列要清零)。

发现新的边界问题:

第一行和第一列本身也是数据。如果它们自己作为"账本",那它们原本是否包含 0 的信息就会被覆盖。

解决方案:先用两个变量记录"第一行原本是否有 0"和"第一列原本是否有 0",然后再开始借用它们。


第四步:代码实现

让我们按照上述逻辑编写 C++ 代码:

1. 初始化标记

首先,我们要确定第一行和第一列是否本来就需要清零。

cpp 复制代码
bool row0_has_zero = false;
bool col0_has_zero = false;

// 检查第一列
for (int i = 0; i < m; i++) {
    if (matrix[i][0] == 0) col0_has_zero = true;
}
// 检查第一行
for (int j = 0; j < n; j++) {
    if (matrix[0][j] == 0) row0_has_zero = true;
}

2. 遍历剩余部分并打标

我们利用第一行和第一列来记录 0 的出现情况。

cpp 复制代码
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; // 标记列
        }
    }
}

3. 根据标记清零

根据我们刚才在第一行/列留下的"脚印",把内部元素清零。

cpp 复制代码
for (int i = 1; i < m; i++) {
    for (int j = 1; j < n; j++) {
        if (matrix[i][0] == 0 || matrix[0][j] == 0) {
            matrix[i][j] = 0;
        }
    }
}

4. 最后处理"账本"本身

别忘了,我们第一步记录的 row0_has_zero 还没用呢。

cpp 复制代码
if (col0_has_zero) {
    for (int i = 0; i < m; i++) matrix[i][0] = 0;
}
if (row0_has_zero) {
    for (int j = 0; j < n; j++) matrix[0][j] = 0;
}

完整代码如下:

cpp 复制代码
void setZeroes(vector<vector<int>>& matrix) {
    int m = matrix.size();
    int n = matrix[0].size();
    bool row0 = false, col0 = false;

    for(int i=0; i<m; i++) if(matrix[i][0] == 0) col0 = true;
    for(int j=0; j<n; j++) if(matrix[0][j] == 0) row0 = true;

    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++) {
        for(int j=1; j<n; j++) {
            if(matrix[i][0] == 0 || matrix[0][j] == 0) matrix[i][j] = 0;
        }
    }

    if(col0) for(int i=0; i<m; i++) matrix[i][0] = 0;
    if(row0) for(int j=0; j<n; j++) matrix[0][j] = 0;
}

通过第一性原理,我们将问题的复杂度从 降到了 的额外空间。

相关推荐
tankeven几秒前
HJ163 时津风的资源收集
c++·算法
Boop_wu11 分钟前
[Java 算法] 动态规划(4)
数据结构·算法·leetcode
旖-旎13 分钟前
分治(计算右侧小于当前元素的个数)(7)
c++·学习·算法·leetcode·排序算法·归并排序
cxr82826 分钟前
细胞球运动追踪的卡尔曼滤波与力场插值算法 —— 活体内微米级颗粒实时定位与轨迹预测系统
算法
炘爚26 分钟前
C++(流类:istream /ostream/istringstream /ostringstream)
开发语言·c++·算法
爱睡懒觉的焦糖玛奇朵36 分钟前
【工业级落地算法之打架斗殴检测算法详解】
人工智能·python·深度学习·学习·算法·yolo·计算机视觉
练习时长一年44 分钟前
后端开发常用的skill推荐
人工智能·算法·职场和发展
漂流瓶jz1 小时前
UVA-10384 推门游戏 题解答案代码 算法竞赛入门经典第二版
数据结构·算法·深度优先·题解·aoapc·算法竞赛入门经典·uva
汀、人工智能1 小时前
[特殊字符] 第1课:两数之和
数据结构·算法·链表·数据库架构··两数之和
cxr8281 小时前
卡尔曼滤波与力场插值算法代码框架
算法