题目描述
编写一种算法,若M × N矩阵中某个元素为0,则将其所在的行与列清零。
示例 1:
- 输入:
[
[1,1,1],
[1,0,1],
[1,1,1]
] - 输出:
[
[1,0,1],
[0,0,0],
[1,0,1]
]
示例 2:
- 输入:
[
[0,1,2,0],
[3,4,5,2],
[1,3,1,5]
] - 输出:
[
[0,0,0,0],
[0,4,5,0],
[0,3,1,0]
]
代码与思路
方法一:记录为0元素纵坐标的位置
思路
我个人的做法是第一次双层for循环用一个vector记录一下元素为0的纵坐标,第二次双层for循环将元素为0的一行置为零,第三次双层for循环将元素为0的一列置0。
代码
class Solution {
public:
void setZeroes(vector<vector<int>>& matrix) {
int size = matrix[0].size();
vector<int> v2;
for(int i = 0; i < matrix.size(); ++i){
for(int j = 0; j < matrix[0].size(); ++j){
if(matrix[i][j] == 0){
v2.push_back(j);
}
}
}
for(int i = 0; i < matrix.size(); ++i){
for(auto& a : matrix[i]){
if( a == 0){
vector<int> v1 (size,0);
matrix[i] = v1;
break;
}
}
}
for(int i = 0; i < v2.size(); ++i){
for(int j = 0; j < matrix.size(); ++j){
matrix[j][v2[i]] = 0;
}
}
return;
}
};
总结
我的这种解法的时间复杂度是O(m * n),空间复杂度是O(m + n)。但我觉得把我这个解法也太矫情了。
方法二:使用标记数组
思路
我们可以用两个标记数组分别记录每一行和每一列是否有零出现。具体地,我们首先遍历该数组一次,如果某个元素为 0,那么就将该元素所在的行和列所对应标记数组的位置置为 1。最后我们再次遍历该数组,用标记数组更新原数组即可。
代码
class Solution {
public:
void setZeroes(vector<vector<int>>& matrix) {
int m = matrix.size();
int n = matrix[0].size();
vector<int> v1(m),v2(n);
for(int i = 0; i < m; ++i)
for(int j = 0; j < n; ++j)
if(matrix[i][j] == 0)
v1[i] = v2[j] = 1;
for(int i = 0; i < m; ++i)
for(int j = 0; j < n; ++j)
if(v1[i] || v2[j])
matrix[i][j] = 0;
return ;
}
};
总结
时间复杂度:O(m*n)
空间复杂度:O(m+n)
这种解法比我的解法优秀的点在于少遍历了一次矩阵,并且不用反复的创建一个临时数组去赋值元素。
方法三:(优化)使用2个标记变量
思路:
我们可以用矩阵的第一行和第一列来代替方法一种的两个标记数组,从而去达到O(1)的空间复杂度。但是这会导致第一行与第一列中的数据被修改。所以我们用两个标记,去记录矩阵的第一行与第一列的数据有没有出现0的情况,如果有,就记为1就好了。
紧接着,我们去用其他行与其他列去处理第一行与第一列中的数据,之后再用第一行与第一列组成的数组去更新矩阵中的其他数据。最后再用标志变量是否为1来检查第一行与第一列的数据,假如说行标志为1,就将矩阵的第一行全部更新为1,同理,列标志为1,就把矩阵的第一列更新为1
代码
class Solution {
public:
void setZeroes(vector<vector<int>>& matrix) {
int m = matrix.size();
int n = matrix[0].size();
int flag_row = 0,flag_col = 0;
for(int i = 0; i < m; ++i)
if(!matrix[i][0]) flag_row = 1;
for(int j = 0; j < n; ++j)
if(!matrix[0][j]) flag_col = 1;
for(int i = 1; i < m; ++i)
for(int j = 1; j < n; ++j)
if(!matrix[i][j]) matrix[i][0] = matrix[0][j] = 0;
for(int i = 1; i < m; ++i)
for(int j = 1; j < n; ++j)
if(!matrix[i][0] || !matrix[0][j]) matrix[i][j] = 0;
if(flag_row)
for(int i = 0; i < m; ++i)
matrix[i][0] = 0;
if(flag_col)
for(auto& a : matrix[0])
a = 0;
return ;
}
};
总结
时间复杂度O(m*n),其中 m 是矩阵的行数,n 是矩阵的列数。我们至多只需要遍历该矩阵两次。
空间复杂度O(1),我们只需要若干变量即可。
方法四:(优化)使用一个标记变量
思路
我们还可以再次将方法三进行优化,其实可以发现,我们可以只把第一列作为标准数组,然后再找一个标志位去记录第一列有没有出现0就可以了。
但为了防止每一列的第一个元素被提前更新,我们需要从最后一行开始,倒序地处理矩阵元素。
代码
class Solution {
public:
void setZeroes(vector<vector<int>>& matrix) {
int m = matrix.size();
int n = matrix[0].size();
int flag_col = 0;
for(int i = 0; i < m; ++i){
if(!matrix[i][0]) flag_col = 1;
for(int j = 1; j < n; ++j)
if(!matrix[i][j]) matrix[i][0] = matrix[0][j] = 0;
}
for(int i = m-1; i >= 0; --i){
for(int j = 1; j < n; ++j)
if(!matrix[i][0] || !matrix[0][j]) matrix[i][j] = 0;
if(flag_col) matrix[i][0] = 0;
}
return ;
}
};
总结
时间复杂度:O(m*n),其中 m 是矩阵的行数,n 是矩阵的列数。我们至多只需要遍历该矩阵两次。
空间复杂度:O(1)。我们只需要常数空间存储若干变量。
总结
整体总结一下,虽然说我第一开始用属于自己的方法,把题解出来了,但是具体的思想体系的并不是很明确,还遍历了3次矩阵,通过学习其他大神的做法,我成功的将遍历矩阵的次数减少了一次,并且感觉教会了我对用变量去标记矩阵。