zoukankan      html  css  js  c++  java
  • [真题]——矩阵置零

    矩阵置零(更新新方法)

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

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

    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执行后,将修改了后续的矩阵元素,这样就会再次影响其他位置的元素。举个例子:

    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的元素置零,具体操作如下:

    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,那么只需要对该行进行一次置零操作,而不是多次执行置零操作;对于列一样。因此,可以将待置零的行和列单独保存,然后统一进行置零操作。具体代码如下:

    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。具体代码如下:

    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的元素。首先看下面的例子:

          *  *  *  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所在的行列。

    具体代码如下。

    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/

  • 相关阅读:
    lintcode:Find the Connected Component in the Undirected Graph 找出无向图汇总的相连要素
    lintcode:Recover Rotated Sorted Array恢复旋转排序数组
    lintcode:Number of Islands 岛屿的个数
    lintcode :Trailing Zeros 尾部的零
    lintcode:Flip Bits 将整数A转换为B
    lintcode:strStr 字符串查找
    lintcode:Subtree 子树
    lintcode 容易题:Partition Array by Odd and Even 奇偶分割数组
    lintcode:在二叉查找树中插入节点
    lintcode:在O(1)时间复杂度删除链表节点
  • 原文地址:https://www.cnblogs.com/eudiwffe/p/6298027.html
Copyright © 2011-2022 走看看