[真题]——矩阵置零

矩阵置零(更新新方法)

题目描述:有一个M行N列的矩阵,每个元素值为0或1,将值为0的元素所在行和列都置为0.

乍一看题目很简单,如果不考虑时间、空间复杂度,那么很容写出下面的代码:

1
2
3
4
5
6
7
8
9
10
void setzero(int matrix[][], int m, int n, int row, int col){
    for (int i=0; i<m; matrix[i++][col]=0);
    for (int i=0; i<n; matrix[row][i++]=0);
}
void setmatrix(int matrix[][], int m, int n){
    for (int i=0; i<m; ++i)
        for (int j=0; j<n; ++j)
            if (matrix[i][j]==0)
                setzero(matrix,m,n,i,j);
}

然而上面的代码并不正确。因为在置零函数setzero执行后,将修改了后续的矩阵元素,这样就会再次影响其他位置的元素。举个例子:

1
2
3
4
matrix:         expect:         setmatrix:
 1 1 1 1        1 1 0 1         1 0 0 0
 1 1 0 1        0 0 0 0         0 0 0 0
 1 1 1 1        1 1 0 1         0 0 0 0

我们期望的正确结果是expect,但我们的代码实际结果是setmatrix。因为在处理matrix[1][2]时,这是第一个值为0的元素,如果经过setzero操作,那么立刻变成expect的结果。但是我们的程序继续运行,就会把置换后的0当做元素原始值0进行操作,因此就会发现第二个为0的元素matrix[1][3],然后会再一次置零setzero。这样,我们的代码就像病毒一样传染整个矩阵,而且会重复置零,效率太差。因此,我们需要将我们手动置零的元素与原始为0的元素做一个区分,例如将手动置零的元素临时置为2,遍历一遍结束后,再把值为2的元素置零,具体操作如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void setzero(int matrix[][], int m, int n, int row, int col){
    for (int i=0; i<m; ++i)
        if (matrix[i][col]==1)
            matrix[i][col]=2;
    for (int i=0; i<n; ++i)
        if (matrix[row][i]==1)
            matrix[row][i]=2;
}
void setmatrix(int matrix[][], int m, int n){
    for (int i=0; i<m; ++i)
        for (int j=0; j<n; ++j)
            if (matrix[i][j]==0)
                setzero(matrix,m,n,i,j);
    for (int i=0; i<m; ++i)
        for (int j=0; j<n; ++j)
            if (matrix[i][j]==2)
                matrix[i][j]=0;
}

显然这是一种暴力方法。如果一个矩阵中存在大量的0元素,那么对于某些元素将进行重复多次的置零操作。如果减少重复置零的次数,就可以优化其执行效率。

对于在同一行中的元素,如果有多个元素值为0,那么只需要对该行进行一次置零操作,而不是多次执行置零操作;对于列一样。因此,可以将待置零的行和列单独保存,然后统一进行置零操作。具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void setmatrix(int matrix[][], int m, int n){
    int row[m], col[n];
    for (int i=0; i<m; row[i++]=1);
    for (int i=0; i<n; col[i++]=1);
    for (int i=0; i<m; ++i)
        for (int j=0; j<n; ++j)
            if (matrix[i][j]==0)
                row[i]=0, col[j]=0;
    for (int i=0; i<m; ++i){
        if (row[i]) continue;
        for (int j=0; j<n; matrix[i][j++]=0);
    }
    for (int i=0; i<n; ++i){
        if (col[i]) continue;
        for (int j=0; j<m; matrix[j++][i]=0);
    }
}

首先定义两个一维数组row和col,并将其初始置为1。这里暂不考虑C语言可变长度数组定义问题。第一趟遍历矩阵,仅找到待置零的行和列;接下来将需要置零的行和列置零。这样可以降低重复置零的次数。

到这一步,时间方面已经很难继续优化了,但是空间上,使用了两个数组,即空间复杂度为O(M+N)。如何进一步优化空间?我们可以使用矩阵的第0行和第0列作为上面代码的row和col数组,即空间复杂度为O(1)。同样需要注意:如果第0行或第0列的元素原本不是0,此时需要将其置为0,那么需要对这种情况进行区别处理,可以沿用暴力法中的置2。具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
void setmatrix(int matrix[][], int m, int n){
    for (int i=1; i<m; ++i)
        for (int j=1; j<n; ++j)
            if (matrix[i][j]==0){
                matrix[i][0] = (matrix[i][0] ? 2:0);
                matrix[0][j] = (matrix[0][j] ? 2:0);
            }
    for (int i=0; i<m; ++i){
        if (matrix[i][0]==1) continue;
        for (int j=1; j<n; matrix[i][j++]=0);
    }
    for (int i=0; i<n; ++i){
        if (matrix[0][i]==1) continue;
        for (int j=1; j<m; matrix[j++][i]=0);
    }
    for (int i=0; i<m; ++i){
        if (matrix[i][0]==0){
            for (int j=0; j<m; matrix[j++][0]=0);
            break;
        }
        else if (matrix[i][0]==2) matrix[i][0]=0;
    }
    for (int i=0; i<n; ++i){
        if (matrix[0][i]==0){
            for (int j=0; j<n; matrix[0][j++]=0);
            break;
        }
        else if (matrix[0][i]==2) matrix[0][i]=0;
    }
}

其中8~15行将第1~m-1行以及第1~n-1列的矩阵元素置为0;16~29行进行收尾操作,将第0行和第0列的元素置零操作。(这里暂不考虑C语言二维数组的传参问题

推广矩阵置零

如果矩阵中的元素除了0之外,其他的元素并不固定为1,而是存在2,3,等其他任意整数,如何操作呢?

显然,使用临时置中间值(此处的2)的方法并不完美,也就是说:对于原始为0经0影响后为0的两种0元素的处理方法,无法继续使用置一个特殊值来区分。如果使用临时存储空间O(M+N)仍然可正常工作,但是如果使用常数级空间,则需要另寻新解。

可以使用虚拟的行和列,存储原始为0的元素。首先看下面的例子:

1
2
3
4
5
*  *  *  0         *  O  O  0        0  0  0  0
*  0  *  *  --->   *  0  *  O  --->  0  0  0  0
*  *  0  *         *  *  0  O        0  0  0  0
*  *  *  *         *  *  *  *        *  0  0  0
    (1)                (2)               (3)

图1和图2的最终结果是一样的,都为图三。其中0表示元素原始为0的位置,O表示将其转换到虚拟行列(此处row=0,col=3)的情况。例如matrix[1][1]=0,将其所在的行列与元素matrix[0][3]=0的交点置为0,经过转换后,图1和图2的尽管不一样,但是其置零结果都为图3.

注意观察特点,根据这种转换,所有原始为0的元素与第一个为0的元素(此处为matrix[0][3])的交点,都在第一个元素所在的行与列。当得到图2的情况时,以第一个0元素所在的行列作为依据,将整个矩阵除第一个0所在行列置0。最后,单独置第一个元素0所在的行列。

具体代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void setZeroes(vector<vector<int> > &mat) {
    // write your code here
    if (mat.size()<1 || mat[0].size()<1) return;
    int m=mat.size(), n=mat[0].size(), row=-1, col=-1;
    for (int i=0; i<m; ++i){
        for (int j=0; j<n; ++j){
            if (mat[i][j] != 0) continue;
            if (row==-1) row = i, col = j;  // select this row and col
            mat[i][col] = 0;        // to store the zero factor
            mat[row][j] = 0;        // to store the zero factor
        }
    }
    if (row==-1) return;
    for (int i=0; i<m; ++i){
        if (i == row) continue;     // set zero except row
        for (int j=0; j<n; ++j){
            if (j == col) continue; // set zero except col
            if (mat[row][j]==0 || mat[i][col]==0) mat[i][j]=0;
        }
    }
    for(int i=m; i; mat[--i][col]=0);   // set zero for row
    for(int j=n; j; mat[row][--j]=0);   // set zero for col
}

此部分代码见https://git.oschina.net/eudiwffe/lintcode/blob/master/C++/set-matrix-zeroes.cpp

LintCode题目http://www.lintcode.com/zh-cn/problem/set-matrix-zeroes/

posted @   eudiwffe  阅读(1886)  评论(1编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!
点击右上角即可分享
微信分享提示