「这是我参与2022首次更文挑战的第1天,活动详情查看:2022首次更文挑战」。
描述
给定一个 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]]
做题
这里原地算法的意思是,不要新建一个对象去存放结果,而是要直接在输入的对象上去修改。
标记行列法
思路: 这道题的意思是,当某一个位置出现了 0,那这个位置的行和列上的数字都要变成 0。
那我们第一步要做的事情就是遍历整个数组,找到 0,然后记录下这个行数和列数,可以使用 set 或者数组来记录(Set 记录行列数,数组做标记)。
然后再遍历一次数组,只要行数或者列数是我们记录过的,就赋值当前位置为 0。
数组
public void setZeroes(int[][] matrix) {
int m = matrix.length,n=matrix[0].length;
boolean[] row=new boolean[m];
boolean[] col=new boolean[n];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (matrix[i][j]!=0){
continue;
}
row[i]=true;
col[j]=true;
}
}
//赋值数组
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (row[i] || col[j]){
matrix[i][j]=0;
}
}
}
}
Set
public void setZeroes1(int[][] matrix) {
int m = matrix.length,n=matrix[0].length;
Set<Integer> rowSet = new HashSet();
Set<Integer> colSet = new HashSet();
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (matrix[i][j]!=0){
continue;
}
rowSet.add(i);
colSet.add(j);
}
}
//赋值数组
Iterator<Integer> rowIterator = rowSet.iterator();
while (rowIterator.hasNext()){
Integer next = rowIterator.next();
for (int i = 0; i < n; i++) {
matrix[next][i]=0;
}
}
Iterator<Integer> colIterator = colSet.iterator();
while (colIterator.hasNext()){
Integer next = colIterator.next();
for (int i = 0; i < m; i++) {
if (rowSet.contains(i)){
continue;
}
matrix[i][next]=0;
}
}
}
Set 版本不尽人意啊,比数组版本的复杂,还效率这么差。
标记变量法
双标记位:
这个做法就是不使用数组或者 set 来存储行列的标记,而是直接使用数组本身来存储,赋值数组的逻辑还是和标记行列法一样,只不过新建数组存储的标记换成了用数组本身上的标记。
于是就是使用了第一行和第一列,但这样我们就不知道第一行和第一列本身是否有 0?
于是还需要两个标记位来标记第一行和第一列原来是否有 0。
public void setZeroes2(int[][] matrix) {
int m = matrix.length,n=matrix[0].length;
boolean rowFlag = false,colFlag = false;
for (int i = 0; i < m; i++) {
if (matrix[i][0] == 0){
colFlag = true;
break;
}
}
for (int i = 0; i < n; i++) {
if (matrix[0][i] == 0){
rowFlag = true;
break;
}
}
//遍历数组
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
if (matrix[i][j]!=0){
continue;
}
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 (rowFlag){
//行
for (int i = 0; i < n; i++) {
matrix[0][i] = 0;
}
}
if (colFlag){
for (int i = 0; i < m; i++) {
matrix[i][0] = 0;
}
}
}
再减少一个标志位
因为使用数组本身来存储标志位,所以我们需要的存储空间是可以是固定的。
同样还是使用第一行和第一列来存储行列是否包含 0 的标记。
这里减去第一行原本是否有 0 的标记,而是第一行和第一列上的相交的元素来标记,第一行和第一列的标记相反也是同理。
具体的逻辑和双标记位的差不多,需要改的地方我在代码中标记出来。
public void setZeroes2(int[][] matrix) {
int m = matrix.length,n=matrix[0].length;
boolean colFlag = false;
for (int i = 0; i < m; i++) {
if (matrix[i][0] == 0){
colFlag = true;
break;
}
}
for (int i = 0; i < n; i++) {
if (matrix[0][i] == 0){
//这里使用 matrix[0][0] 代替标记位!
matrix[0][0]=0;
break;
}
}
//遍历数组
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
if (matrix[i][j]!=0){
continue;
}
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 (matrix[0][0]==0){
//行
for (int i = 0; i < n; i++) {
matrix[0][i] = 0;
}
}
if (colFlag){
for (int i = 0; i < m; i++) {
matrix[i][0] = 0;
}
}
}
芜湖,这个内存消耗超过了 99.37%,虽然只比双标记的少了 0.4 MB。
今天就到这里了。
这里是程序员徐小白,【每日算法】是我新开的一个专栏,在这里主要记录我学习算法的日常,也希望我能够坚持每日学习算法,不知道这样的文章风格您是否喜欢,不要吝啬您免费的赞,您的点赞、收藏以及评论都是我下班后坚持更文的动力。