【问题标题】:Microsoft Interview: transforming a matrix微软采访:转换矩阵
【发布时间】:2012-06-06 02:10:32
【问题描述】:

给定一个大小为 n x m 的矩阵,其中填充了 0 和 1

例如:

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 原本没有0。


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

那么另一个问题来了,如果我找到这样的行和列,我该如何将对应的单元格a[r][c]标记为special,因为它已经是0了。


我的直觉是我应该对此使用某种位操作。或者为了满足所需的复杂性,我必须执行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 之类的操作。

即使在不考虑时间复杂度的情况下进行暴力破解,我也无法在其他条件下解决。

有人知道吗?


解决方案:Java 版本

@japreiss 已经回答了这个问题,他/她的回答很聪明而且正确。他的代码是 Python 的,现在我给出 Java 版本。学分都归@japreiss

public class MatrixTransformer {

    private int[][] a;
    private int m;
    private int n;
    
    public MatrixTransformer(int[][] _a, int _m, int _n) {
        a = _a;
        m = _m;
        n = _n;
    }
    
    private int scanRow(int i) {
        int allZero = 0;
        for(int k = 0;k < n;k++)
            if (a[i][k] == 1) {
                allZero = 1;
                break;
            }
        
        return allZero;
    }
    
    private int scanColumn(int j) {
        int allZero = 0;
        for(int k = 0;k < m;k++)
            if (a[k][j] == 1) {
                allZero = 1;
                break;
            }
        
        return allZero;
    }
    
    private void setRowToAllOnes(int i) {
        for(int k = 0; k < n;k++)
            a[i][k] = 1;
    }
    
    private void setColToAllOnes(int j) {
        for(int k = 0; k < m;k++)
            a[k][j] = 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
    
        
        
    public void transform() {
        int firstRow = scanRow(0);
        int firstCol = scanColumn(0);
                
                
        for(int k = 0;k < n;k++) {
            a[0][k] = scanColumn(k);
        }

        // 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

        for(int k = 0;k < m;k++) {
            a[k][0] = scanRow(k);
        }
        
        a[0][0] = firstCol | firstRow;
        
        for (int i = 1;i < m;i++)
            for(int j = 1;j < n;j++)
                a[i][j] = a[0][j] | a[i][0];


        
        if (firstRow == 1) {
            setRowToAllOnes(0);
        }
        
        if (firstCol == 1) 
            setColToAllOnes(0);
    }
    
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        
        for (int i = 0; i< m;i++) {
            for(int j = 0;j < n;j++) {
                sb.append(a[i][j] + ", ");
            }
            sb.append("\n");
        }
        
        return sb.toString();
    }
    /**
     * @param args
     */
    public static void main(String[] args) {
        int[][] a = {{1, 1, 0, 1, 0}, {0, 0, 0, 0, 0},{0, 1, 0, 0, 0},{1, 0, 1, 1, 0}};
        MatrixTransformer mt = new MatrixTransformer(a, 4, 5);
        mt.transform();
        System.out.println(mt);
    }

}

【问题讨论】:

标签: algorithm matrix bit-manipulation space-complexity


【解决方案1】:

这是另一种直觉,它给出了解决问题的简洁算法。

使用 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,所以我们只需用 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

现在,我们填写 aux 数组:

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

现在,我们根据 aux 数组填充列:

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)。
  • 做 O(mn) 工作来填充辅助数组。
  • 做 O(mn) 工作来填充包含 1 的行。
  • 做 O(mn) 工作来填充包含 1 的列。

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

编辑:我有一个 Java implementation of this algorithm,在我的个人网站上提供了详细描述它的 cmets。享受吧!

希望这会有所帮助!

【讨论】:

    【解决方案2】:

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

    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
    

    【讨论】:

    • 我从最初的帖子中编辑了这个 - 忘记返回并更正第 0 行和第 0 列
    • firstRow 的大小为 n,firstCol 的大小为 m。这不构成常数空间吧?
    • @KDiTraglia 我认为他的 firstRow 和 firstCol 实际上是在重用原始矩阵中真正的第一行和第一列,所以不是多余的
    • firstRowfirstCol 只是单个布尔值。我更改了 scanRow 和 scanColumn 以澄清。
    • 这是一种非常聪明的方法,因为它利用了矩阵的一行和一列。我没有这样想。很好的答案。
    【解决方案3】:
    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;
                }
            }
        }
    }
    

    【讨论】:

    • 这使用了超过 O(1) 的空间(它使用了 O(n + m) 的空间)。
    【解决方案4】:

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

    【讨论】:

    • 递归处理每个象限需要 O(log n) 的调用堆栈空间。
    【解决方案5】:

    假设矩阵是从 0 开始的,即第一个元素在 mat[0][0]

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

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

    此时我们已经获得了关于应该将哪一列和哪一行设置为 1 的完整信息

    1. 再次开始扫描从 mat[1][1] 开始的内部矩阵并设置每个元素 如果当前行或列在表头中包含 1,则为 1: if ( (mat[row][0] == 1) || (mat[0][col] == 1) ) 然后将 mat[row][col] 设置为 1。

    此时我们已经处理了内部矩阵中的所有单元格,我们是 尚未处理表头本身

    1. 处理表头 如果 matt[0][0] == 1 然后设置第一列中的所有元素,然后首先 第 1 行
    2. 完成

    时间复杂度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查看我的实现

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-04-26
      • 2014-09-15
      • 2013-01-12
      • 2016-10-20
      • 1970-01-01
      • 2014-01-19
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多