矩阵置零问题的原地算法
- 问题描述
- 示例
- 约束条件
- 进阶要求
- 问题分析
- 难点分析
- 解题思路
- 代码实现
- 代码说明
- 测试用例
- 测试用例 1
- 测试用例 2
- 测试用例 3
- 总结
问题描述
给定一个 m x n
的矩阵,如果矩阵中的某个元素为 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
-2^31 <= matrix[i][j] <= 2^31 - 1
进阶要求
- 一个直观的解决方案是使用 O(mn) 的额外空间,但这并不是一个好的解决方案。
- 一个简单的改进方案是使用 O(m + n) 的额外空间,但这仍然不是最好的解决方案。
- 能否实现一个仅使用 常量空间 的解决方案?
问题分析
本题要求将矩阵中某些行和列置零,且必须使用原地算法。直接遍历矩阵并将对应行和列置零会导致信息丢失,因此需要一种方法来标记哪些行和列需要被置零。
难点分析
- 标记零的位置:如何在不使用额外矩阵的情况下记录哪些行和列需要被置零。
- 原地修改:在标记完成后,如何在不破坏标记信息的情况下完成置零操作。
- 第一行和第一列的特殊情况:如果第一行或第一列本身包含零,直接使用它们作为标记可能会导致误修改。
解题思路
我们可以通过以下步骤实现原地算法:
-
使用第一行和第一列作为标记:
- 遍历矩阵,如果某个元素为 0,则将该元素所在行的第一个元素和所在列的第一个元素置为 0。这样,第一行和第一列就成为了标记行和列是否需要置零的“索引”。
-
处理第一行和第一列的特殊情况:
- 如果第一行或第一列本身存在 0,则需要额外记录,因为它们会被用作标记,可能会被误修改。
-
根据标记置零:
- 遍历第一行和第一列的标记,将对应的行和列置零。
-
处理第一行和第一列:
- 最后根据之前记录的特殊情况,决定是否将第一行和第一列整体置零。
代码实现
以下是完整的 C 代码实现:
void setZeroes(int** matrix, int matrixSize, int* matrixColSize) {int firstRowZero = 0; // 标记第一行是否需要置零int firstColZero = 0; // 标记第一列是否需要置零// 检查第一行是否需要置零for (int j = 0; j < *matrixColSize; j++) {if (matrix[0][j] == 0) {firstRowZero = 1;break;}}// 检查第一列是否需要置零for (int i = 0; i < matrixSize; i++) {if (matrix[i][0] == 0) {firstColZero = 1;break;}}// 使用第一行和第一列作为标记for (int i = 1; i < matrixSize; i++) {for (int j = 1; j < *matrixColSize; j++) {if (matrix[i][j] == 0) {matrix[i][0] = 0; // 标记该行需要置零matrix[0][j] = 0; // 标记该列需要置零}}}// 根据标记置零(跳过第一行和第一列)for (int i = 1; i < matrixSize; i++) {for (int j = 1; j < *matrixColSize; j++) {if (matrix[i][0] == 0 || matrix[0][j] == 0) {matrix[i][j] = 0;}}}// 处理第一行if (firstRowZero) {for (int j = 0; j < *matrixColSize; j++) {matrix[0][j] = 0;}}// 处理第一列if (firstColZero) {for (int i = 0; i < matrixSize; i++) {matrix[i][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]
]
测试用例 3
输入:
matrix = [[1, 2, 3, 4],[5, 0, 7, 8],[9, 10, 11, 12]
]
输出:
[[1, 0, 3, 4],[0, 0, 0, 0],[9, 0, 11, 12]
]
总结
本题通过利用矩阵的第一行和第一列作为标记,实现了原地置零的功能。这种方法不仅满足了原地算法的要求,还具有较高的效率。时间复杂度为 O(m × n),空间复杂度为 O(1)。