给定一个 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
-231 <= matrix[i][j] <= 231 - 1
进阶:
- 一个直观的解决方案是使用
O(mn)
的额外空间,但这并不是一个好的解决方案。 - 一个简单的改进方案是使用
O(m + n)
的额外空间,但这仍然不是最好的解决方案。 - 你能想出一个仅使用常量空间的解决方案吗?
方法一:使用额外集合(O (m+n) 空间)
这种方法通过两个集合记录需要置零的行和列,然后遍历矩阵完成置零操作。
function setZeroes(matrix: number[][]): void {
const m = matrix.length;
const n = matrix[0].length;
const rows = new Set<number>();
const cols = new Set<number>();
// 记录需要置零的行和列
for (let i = 0; i < m; i++) {
for (let j = 0; j < n; j++) {
if (matrix[i][j] === 0) {
rows.add(i);
cols.add(j);
}
}
}
// 将记录的行和列置零
for (let i = 0; i < m; i++) {
for (let j = 0; j < n; j++) {
if (rows.has(i) || cols.has(j)) {
matrix[i][j] = 0;
}
}
}
}
方法二:使用矩阵行列标记(O (1) 空间)
利用矩阵的第一行和第一列作为标记,避免使用额外空间,同时用两个变量记录第一行和列是否需要置零。
function setZeroes(matrix: number[][]): void {
const m = matrix.length;
const n = matrix[0].length;
let firstRowHasZero = false;
let firstColHasZero = false;
// 检查第一行是否需要置零
for (let j = 0; j < n; j++) {
if (matrix[0][j] === 0) {
firstRowHasZero = true;
break;
}
}
// 检查第一列是否需要置零
for (let i = 0; i < m; i++) {
if (matrix[i][0] === 0) {
firstColHasZero = true;
break;
}
}
// 使用第一行和第一列记录其他行和列是否需要置零
for (let i = 1; i < m; i++) {
for (let j = 1; j < n; j++) {
if (matrix[i][j] === 0) {
matrix[i][0] = 0;
matrix[0][j] = 0;
}
}
}
// 根据第一行和第一列的标记,置零矩阵(除第一行和列)
for (let i = 1; i < m; i++) {
for (let j = 1; j < n; j++) {
if (matrix[i][0] === 0 || matrix[0][j] === 0) {
matrix[i][j] = 0;
}
}
}
// 处理第一行和第一列的置零
if (firstRowHasZero) {
for (let j = 0; j < n; j++) {
matrix[0][j] = 0;
}
}
if (firstColHasZero) {
for (let i = 0; i < m; i++) {
matrix[i][0] = 0;
}
}
}
方法三:两次遍历直接标记(O (1) 空间变种)
另一种原地算法,通过两次遍历实现置零,第一次遍历标记非零元素,第二次置零。
function setZeroes(matrix: number[][]): void {
const m = matrix.length;
const n = matrix[0].length;
// 第一次遍历:将非零元素标记为特殊值(如Infinity)
for (let i = 0; i < m; i++) {
for (let j = 0; j < n; j++) {
if (matrix[i][j] === 0) {
// 标记所在行的非零元素
for (let k = 0; k < n; k++) {
if (matrix[i][k] !== 0) {
matrix[i][k] = Infinity;
}
}
// 标记所在列的非零元素
for (let k = 0; k < m; k++) {
if (matrix[k][j] !== 0) {
matrix[k][j] = Infinity;
}
}
}
}
}
// 第二次遍历:将标记为Infinity的元素置零
for (let i = 0; i < m; i++) {
for (let j = 0; j < n; j++) {
if (matrix[i][j] === Infinity) {
matrix[i][j] = 0;
}
}
}
}
复杂度分析
方法 | 时间复杂度 | 空间复杂度 | 说明 |
---|---|---|---|
方法一(集合标记) | O(m×n) | O(m+n) | 使用两个集合记录置零的行和列,实现简单,空间复杂度优于 O (m×n) |
方法二(行列标记) | O(m×n) | O(1) | 利用矩阵自身的行列作为标记,空间复杂度最优,实现稍复杂 |
方法三(直接标记) | O(m×n) | O(1) | 通过特殊值标记非零元素,避免使用额外空间,但可能受限于数值范围 |
测试示例
// 测试示例1
const matrix1 = [
[1, 1, 1],
[1, 0, 1],
[1, 1, 1]
];
setZeroes(matrix1);
console.log(matrix1); // 输出: [[1,0,1],[0,0,0],[1,0,1]]
// 测试示例2
const matrix2 = [
[0, 1, 2, 0],
[3, 4, 5, 2],
[1, 3, 1, 5]
];
setZeroes(matrix2);
console.log(matrix2); // 输出: [[0,0,0,0],[0,4,5,0],[0,3,1,0]]
方法说明
-
方法一是最直观的解决方案,通过集合记录需要置零的行和列,适合理解问题本质,但空间复杂度为 O (m+n)。
-
方法二是最优解决方案,利用矩阵的第一行和第一列作为标记,将空间复杂度降为 O (1)。需要特别注意处理第一行和第一列本身是否需要置零的情况,通过两个布尔变量
firstRowHasZero
和firstColHasZero
来记录。 -
方法三通过特殊值标记非零元素,避免使用额外空间,但存在局限性:如果矩阵中本身包含该特殊值(如 Infinity),则需要选择其他标记值。这种方法在数值范围允许时有效,否则可能需要调整标记策略。
在实际应用中,方法二是最推荐的解决方案,既满足原地修改的要求,又具有最优的空间复杂度。