微软访谈:转换矩阵

时间:2012-05-31 19:28:47

标签: algorithm matrix bit-manipulation space-complexity

  

给定一个大小为n x m且填充0和1的矩阵

     

e.g:

     

1 1 0 1 0

     

0 0 0 0 0

     

0 1 0 0 0

     

1 0 1 1 0

     

如果矩阵在(i,j)处有1,则用第1列填充第j列和第i行

     

即,我们得到:

     

1 1 1 1 1

     

1 1 1 1 0

     

1 1 1 1 1

     

1 1 1 1 1

     

所需复杂度:O(n * m)时间和O(1)空间

     

注意:您不得在矩阵条目中存储除“0”或“1”之外的任何内容

以上是微软面试问题。

我现在想了两个小时。我有一些线索,但不能再继续了。


确定。这个问题的第一个重要部分是Even using a straight forward brute-force way,它不容易解决。

如果我只使用两个循环迭代矩阵中的每个单元格,并更改相应的行和列,则无法完成,因为生成的矩阵应基于原始矩阵。

例如,如果我看到a[0][0] == 1,则无法将row 0column 0全部更改为1,因为这会影响row 1 row 1 {1}}最初没有0。


我注意到的第二件事是,如果行r仅包含0而列c仅包含0,那么a[r][c]必须为{ {1}};对于不属于此模式的任何其他职位,应为0

然后又出现了另一个问题,如果我找到这样的行和列,我怎样才能将相应的单元格1标记为a[r][c],因为它已经是0。


我的直觉是我应该对此使用某种位操作。或者为了满足所需的复杂性,我必须执行special

之类的操作

即使对于没有考虑时间复杂性的蛮力,我也无法用其他条件来解决它。

任何人都有线索?


解决方案:Java版

@japreiss回答了这个问题,他/她的答案是明智和正确的。他的代码是用Python编写的,现在我给出了Java版本。积分全部转到@japreiss

After I take care of a[i][j], I should then proceed to deal with a[i+1][j+1], instead of scan row by row or column by column

5 个答案:

答案 0 :(得分:19)

这是python伪代码中的一个解决方案,它使用了2个额外的bool存储空间。我认为这比我用英语更清楚。

def scanRow(i):
    return 0 if row i is all zeroes, else 1

def scanColumn(j):
    return 0 if col j is all zeroes, else 1

# we're going to use the first row and column
# of the matrix to store row and column scan values,
# but we need aux storage to deal with the overlap
firstRow = scanRow(0)
firstCol = scanCol(0)

# scan each column and store result in 1st row - O(mn) work
for col in range(1, n):
    matrix[0, col] = scanColumn(col)

# now row 0 tells us whether each column is all zeroes or not
# it's also the correct output unless row 0 contained a 1 originally

# do the same for rows into column 0 - O(mn) work
for row in range(1, m):
    matrix[row, 0] = scanRow(row)

matrix[0,0] = firstRow or firstCol

# now deal with the rest of the values - O(mn) work
for row in range(1, m):
    for col in range(1, n):
        matrix[row, col] = matrix[0, col] or matrix[row, 0]


# 3 O(mn) passes!

# go back and fix row 0 and column 0
if firstRow:
    # set row 0 to all ones

if firstCol:
    # set col 0 to all ones

答案 1 :(得分:7)

这是另一种直觉,为解决问题提供了一种简洁的算法。

使用O(n)空间的初始算法。

现在,让我们忽略O(1)内存约束。假设你可以使用O(n)内存(如果矩阵是m×n)。这将使这个问题变得更加容易,我们可以使用以下策略:

  • 创建一个布尔数组,每列一个条目。
  • 对于每列,确定列中是否有任何1,并将该信息存储在相应的数组条目中。
  • 对于每一行,如果行中有任何1,则将该行设置为全1。
  • 对于每一列,如果设置了相应的数组条目,则将该列设置为全1。

举个例子,考虑一下这个数组:

1 1 0 1 0
0 0 0 0 0
0 1 0 0 0
1 0 1 1 0

我们首先创建和填充辅助数组,这可以通过一次访问每一列来及时完成O(mn)。这显示在这里:

1 1 0 1 0
0 0 0 0 0
0 1 0 0 0
1 0 1 1 0

1 1 1 1 0  <--- aux array

接下来,我们遍历行并填充每个行,如果它包含任何1。这给出了这个结果:

1 1 1 1 1
0 0 0 0 0
1 1 1 1 1
1 1 1 1 1

1 1 1 1 0  <--- aux array

最后,如果辅助阵列在该位置具有1,则我们用1填充每列。这显示在这里:

1 1 1 1 1
1 1 1 1 0
1 1 1 1 1
1 1 1 1 1

1 1 1 1 0  <--- aux array

所以有一个问题:这使用O(n)空间,这是我们没有的!那么为什么要走这条路呢?

使用O(1)空间的修正算法。

事实证明,我们可以使用一个非常可爱的技巧来使用O(1)空间运行此算法。我们需要一个关键观察:如果每行包含至少一个1,则整个矩阵变为1。因此我们首先看看是否是这种情况。如果是的话,太好了!我们已经完成了。

否则,矩阵中必须有一些全部为0的行。由于该行全为0,我们知道在“填充包含1和1的每一行”步骤中,行将不会被填充。因此,我们可以使用该行作为辅助数组!

让我们看看这一点。从这开始:

1 1 0 1 0
0 0 0 0 0
0 1 0 0 0
1 0 1 1 0

现在,我们可以在其中找到一个包含全0的行并将其用作辅助数组:

1 1 0 1 0
0 0 0 0 0  <-- Aux array
0 1 0 0 0
1 0 1 1 0

我们现在通过查看每个列并标记哪些包含至少一个1:

来填充辅助数组
1 1 0 1 0
1 1 1 1 0  <-- Aux array
0 1 0 0 0
1 0 1 1 0

在这里填写1是完全安全的,因为我们知道他们无论如何都会被填补。现在,对于包含1,的每一行(辅助数组行除外),我们用1填充这些行:

1 1 1 1 1
1 1 1 1 0  <-- Aux array
1 1 1 1 1
1 1 1 1 1

我们跳过辅助数组,因为最初它全是0,所以它通常不会被填充。最后,我们在辅助数组中用1填充每列,给出最终结果:

1 1 1 1 1
1 1 1 1 0  <-- Aux array
1 1 1 1 1 
1 1 1 1 1

我们再做一个例子。考虑这个设置:

1 0 0 0
0 0 1 0
0 0 0 0
0 0 1 0

我们首先找到一个全零的行,如下所示:

1 0 0 0
0 0 1 0
0 0 0 0 <-- Aux array
0 0 1 0

接下来,让我们通过标记包含1:

的列来填充该行
1 0 0 0
0 0 1 0
1 0 1 0 <-- Aux array
0 0 1 0

现在,填写包含1:

的所有行
1 1 1 1
1 1 1 1
1 0 1 0 <-- Aux array
1 1 1 1

接下来,用1代填充aux数组中包含1的所有列。这已经在这里完成了,我们得到了结果!

作为另一个例子,考虑这个数组:

1 0 0
0 0 1
0 1 0

这里的每一行至少包含一个1,所以我们只用矩阵填充矩阵并完成。

最后,让我们试试这个例子:

0 0 0 0 0
0 0 0 0 0
0 1 0 0 0
0 0 0 0 0
0 0 0 1 0

我们有很多辅助阵列的选择,所以让我们选择第一行:

0 0 0 0 0 <-- aux array
0 0 0 0 0
0 1 0 0 0
0 0 0 0 0
0 0 0 1 0

现在,我们填写辅助数组:

0 1 0 1 0 <-- aux array
0 0 0 0 0 
0 1 0 0 0
0 0 0 0 0
0 0 0 1 0

现在,我们填写行:

0 1 0 1 0 <-- aux array
0 0 0 0 0 
1 1 1 1 1
0 0 0 0 0
1 1 1 1 1

现在,我们根据辅助数组填写列:

0 1 0 1 0 <-- aux array
0 1 0 1 0 
1 1 1 1 1
0 1 0 1 0
1 1 1 1 1

我们已经完成了!整个事情都在O(mn)时间运行,因为我们

  • O(mn)是否可以找到辅助数组,如果不存在,O(mn)可能会立即工作。
  • Do O(mn)可以填充辅助阵列。
  • Do O(mn)工作以填充包含1的行。
  • Do O(mn)可以填写包含1的列。

另外,它只使用O(1)空间,因为我们只需要存储aux数组的索引和足够的变量来对矩阵进行循环。

编辑:我有 Java implementation of this algorithm ,并在我的个人网站上提供详细说明。享受!

希望这有帮助!

答案 2 :(得分:1)

假设矩阵是基于0的,即第一个元素在mat [0] [0]

  1. 使用第一行和第一列作为表标题分别包含列和行信息。 1.1注意mat [0] [0]处的元素。如果是1,则最后需要特殊处理(稍后描述)

  2. 现在,开始从index [1] [1]扫描内部矩阵到最后一个元素 2.1如果[row] [col] == 1处的元素,则按如下方式更新表头数据    行:mat [row] [0] = 1;    栏目:mat [0] [col] = 1;

  3. 此时我们有完整的信息,列和行应设置为1

    1. 再次从mat [1] [1]开始扫描内部矩阵并设置每个元素 如果当前行或列在表头中包含1,则为1: if((mat [row] [0] == 1)||(mat [0] [col] == 1))然后将mat [row] [col]设置为1。
    2. 此时我们已经处理了内部矩阵中的所有单元格 尚未处理表头文件

      1. 处理表头 如果matt [0] [0] == 1则设置第一列和第一列中的所有元素 排到1
      2. 完成
      3. 时间复杂度O(2 *((n-1)(m-1)+(n + m-1)),即O(2 * n * m - (n + m)+ 1),即O (2 * N * M) 空间O(1)

        请参阅我在http://codepad.org/fycIyflw

        的实施情况

答案 3 :(得分:-1)

另一种解决方案是像往常一样扫描矩阵,并在第一个1中将矩阵分成4个象限。然后,将行和列设置为1,并递归处理每个象限。只需确保设置整个列和行,即使您只扫描象限。

答案 4 :(得分:-1)

public void setOnes(int [][] matrix){

    boolean [] row = new boolean [matrix.length]
    boolean [] col = new boolean [matrix[0].length]
    for (int i=0;i<matrix.length;i++){
        for(int j=0;j<matrix[0].length;j++){
            if (matrix[i][j] == 1){
                row[i] = true
                col[j] = true
            }
        }
    }

    for (int i=0;i<matrix.length;i++){
        for(int j=0;j<matrix[0].length;j++){
            if (row[i] || col[j]){
                matrix[i][j] = 1;
            }
        }
    }
}