我们可以用小于O(n * n)...(nlogn或n)来计算它

时间:2010-09-07 14:20:50

标签: algorithm time-complexity space-complexity

这是一个非常着名的跨国公司向我询问的问题。问题如下......

输入0和1的2D N * N数组。如果A(i,j)= 1,则对应于第i行和第j列的所有值将为1.如果已经存在1,则它仍为1。

例如,如果我们有数组

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

我们应该将输出视为

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

输入矩阵稀疏填充。

这是否可能小于O(N ^ 2)?

没有提供额外的空间是另一个条件。我想知道是否有办法使用空格< = O(N)来实现复杂性。

P.S:我不需要能给出O(N * N)复杂度的答案。这不是一个家庭作业问题。我已经尝试了很多,无法得到一个正确的解决方案,并认为我可以在这里得到一些想法。抛弃打印复杂性

我粗略的想法是可以动态地消除遍历的元素数量,将它们限制在2N左右左右。但我无法理解。

13 个答案:

答案 0 :(得分:8)

在最坏的情况下,您可能需要将N * N - N位从0切换为1以生成输出。看起来你很好地坚持使用O(N * N)。

答案 1 :(得分:6)

我想你可以针对最好的情况对其进行优化,但我很想说你的最坏情况仍然是O(N * N):你最糟糕的情况是所有0的数组,你会必须检查每一个元素。

优化将涉及一旦找到“1”就跳过行或列(我可以提供详细信息,但是你说你不关心O(N * N)“,但除非你有元数据到指示整个行/列是空的,或者除非您有SIMD样式的方法一次检查多个字段(例如,如果每行对齐4,并且您可以读取32位值数据,或者如果您的数据是以位掩码的形式,你将始终必须处理一个全零数组的问题。

答案 2 :(得分:6)

显然,输出矩阵及其否定版本也不必稀疏(将第一行的一半设置为1的矩阵以及其他任何设置为0以查看),因此时间取决于您允许使用的格式输出。 (我假设输入是一个元素列表或等价的东西,否则你无法利用矩阵稀疏。)

O(M + N)空间和时间的简单解决方案(M是输入矩阵中的1的数量):取两个长度为N的数组填充1,迭代输入中的所有数组,每个数组从第一个数组中删除X坐标,从第二个数组中删除Y.输出是两个数组,它们清楚地定义了结果矩阵:如果第一个数组的X坐标和第二个数据的Y坐标为0,则其(X,Y)坐标为0。

更新:取决于语言,您可以使用一些技巧通过多次引用同一行来返回普通的2D数组。例如在PHP中:

// compute N-length arrays $X and $Y which have 1 at the column 
// and row positions which had no 1's in the input matrix
// this is O(M+N)
$result = array();
$row_one = array_fill(0,N,1);
for ($i=0; $i<N; $i++) {
    if ($Y[$i]) {
         $result[$i] = &$row_one;
    } else {
         $result[$i] = &$X;
    }
}
return $result;

当然这只是一个普通的数组,只要你不尝试写它。

答案 3 :(得分:3)

由于必须检查矩阵的每个条目,因此最坏的情况总是为N * N.

使用小的2 * N额外存储空间,您可以在O(N * N)中执行操作。只需为每一行创建一个掩码,为每一列创建另一个掩码 - 扫描数组并随时更新掩码。然后再次扫描以根据蒙版填充结果矩阵。

如果您正在进行输入矩阵更改的操作,则可以为输入的每一行和每列存储非零条目的计数(而不是简单的掩码)。然后,当输入中的条目更改时,您会相应地更新计数。那时,我会完全放弃输出矩阵并直接查询掩码/计数,而不是甚至维护输出矩阵(如果你真的想要保留输出矩阵,也可以在N N时间内更新。周围)。因此,加载初始矩阵仍然是O(N N),但更新可能会少得多。

答案 4 :(得分:3)

输入矩阵可能是稀疏的,但除非您能够以稀疏格式(即最初设置的(i,j)对列表)获取它,否则只读取输入将消耗Ω(n ^ 2)时间。即使输入稀疏,也很容易以O(n ^ 2)输出结束写入。作为作弊,如果您被允许输出设置行和设置列的列表,那么您可以降低到线性时间。当你的算法实际上必须产生比“是”或“否”更重要的结果时,没有什么魔力。

Mcdowella对另一个答案的评论提出了另一种替代输入格式:游程编码。对于稀疏输入,显然需要不超过O(n)的时间来读取它(考虑在0和1之间有多少转换)。然而,从那里它崩溃了。考虑一个输入矩阵,结构如下:

0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 . . . 
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 . . .
.     .
.       .
.         .

即,第一行交替0和1,其他地方交替0。显然稀疏,因为总共有n/2个。但是,RLE输出必须在每一行中重复此模式,从而导致O(n ^ 2)输出。

答案 5 :(得分:2)

你说:

  

我们应该将输出视为......

所以你需要输出整个矩阵,它有N ^ 2个元素。这是O(N * N)。

问题本身不是O(N * N):你不必计算和存储整个矩阵:你只需要两个向量L和C,每个向量大小为N:

如果行x是1行,则L [x]为1,否则为0;

如果第x行是1行,则C [x]为1,否则为0;

你可以在O(N)中构造这些向量,因为初始矩阵是稀疏的;您的输入数据不是矩阵,而是包含每个非零元素的坐标(行,列)的列表。在读取此列表时,设置L [line] = 1且C [column] = 1,问题解决:如果L [l] == 1或C [c] =,则M [l,c] == 1 = 1

答案 6 :(得分:2)

Hii家伙,

感谢mb14的评论,我想我可以在不到O(N N)时间内解决它... 最糟糕的是需要O(N N)......

实际上,我们有给定的数组假设

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

让我们有2个大小为N的数组(这是最糟糕的情况)......一个专门用于索引行和其他列...... 将[i] [1] = 0的那些放在一个数组中,然后将[1] [j] = 0放在另一个数组中..

然后仅获取这些值并检查第二行和列...以这种方式,我们得到行和列的值,其中只有0;完全是...

行数组中的值的数量给出结果数组中的0的数量,并且点[行数组值] [列数组值]给出这些点....

我们可以在O(N N)以下解决它,最差的是O(N N)...正如我们所见,数组(大小为N)减少了...... / p>

我为一些阵列做了这个,得到了所有这些的结果...... :)

如果我错在任何地方,请纠正我......

Thanx对你所有评论的人...你们都非常乐于助人,我确实学到了很多东西...... :)

答案 7 :(得分:1)

显然有O(N^2)项工作要做。在矩阵中

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

所有位必须设置为1,N*(N-1)不设置为1(20,在此5x5情况下)。

相反,您可以提出一个始终在O(N^2)时间内执行此操作的算法:沿顶行和列匹配,如果行或列获得非零答案,请填写整行或柱;然后解决较小的(N-1)x(N-1)问题。

因此存在必须至少N^2的情况,并且N^2可以在没有额外空间的情况下解决任何情况。

答案 8 :(得分:0)

如果你的矩阵是稀疏的,那么复杂性很大程度上取决于输入编码,特别是在NN 2 或类似的东西中没有很好地测量,但就N而言你的输入复杂度M 输出复杂度M out 。我期待O(N + M in + M out ),但很大程度上取决于您可以使用的编码和技巧。

答案 9 :(得分:0)

这完全取决于您的输入数据结构。如果将矩阵(1s和0s)作为2D数组传递,则需要遍历它,即O(N ^ 2)。但是由于您的数据稀疏,如果您只传递1作为输入,则可以这样做,因此ouptut为O(M),其中M不是单元格数,而是1个单元格的数量。它将类似于此(下面的伪代码):

list f(list l) {
   list rows_1;
   list cols_1;

    for each elem in l {
        rows_1[elem.row] = 1;
        cols_1[elem.col] = 1;
    }

    list result;
    for each row in rows_1 {
        for each col in cols_1 {
             if (row == 1 || col == 1) {
                 add(result, new_elem(row, col));
             }
        }
    } 
   return result;
}

答案 10 :(得分:0)

检查值时,请勿填充矩阵的中心。当您浏览元素时,如果您在第一行和第一列中设置了相应的元素。然后回去填写并穿过。

编辑:实际上,这与Andy的相同。

答案 11 :(得分:0)

这取决于您的数据结构。

行只有两种可能的情况:

  • 如果输入
  • 中有元素(i,_),则行i填充1
  • 所有其他行都相同:即如果输入中有元素(_,j),则第j个元素为1。

因此,结果可以紧凑地表示为对行的引用数组。由于我们只需要两行,结果也只会消耗O(N)内存。作为一个例子,这可以在python中实现,如下所示:

def f(element_list, N):
  A = [1]*N
  B = [0]*N
  M = [B]*N
  for row, col in element_list:
    M[row] = A
    B[col] = 1
  return M

示例通话

 f([(1,1),(2,2),(4,3)],5)

结果

[[0, 1, 1, 1, 0], [1, 1, 1, 1, 1], [1, 1, 1, 1, 1], [0, 1, 1, 1, 0], [1, 1, 1, 1, 1]]

重要的一点是这里不复制数组,即M [row] = A只是一个引用的赋值。因此,复杂度为O(N + M),其中M是输入的长度。

答案 12 :(得分:0)

#include<stdio.h>

包括

int main() {     int arr [5] [5] = {{1,0,0,0,0},                     {0,1,1,0,0},                     {0,0,0,0,0},                     {1,0,0,1,0},                     {0,0,0,0,0}};     int var1 = 0,var2 = 0,i,j;

for(i=0;i<5;i++)
   var1 = var1 | arr[0][i];

for(i=0;i<5;i++)
   var2 = var2 | arr[i][0];

for(i=1;i<5;i++)
   for(j=1;j<5;j++)
      if(arr[i][j])
         arr[i][0] = arr[0][j] = 1;

for(i=1;i<5;i++)
   for(j=1;j<5;j++)
          arr[i][j] = arr[i][0] | arr[0][j];

for(i=0;i<5;i++)
   arr[0][i] = var1;

for(i=0;i<5;i++)
   arr[i][0] = var2;

for(i=0;i<5;i++)
{
   printf("\n");             
   for(j=0;j<5;j++)
      printf("%d ",arr[i][j]);
}

getch();

}

该程序仅使用2 4个临时变量(var1,var2,i和j),因此在时间复杂度为O(n ^ 2)的恒定空间中运行。我认为根本不可能解决这个问题问题&lt;为O(n ^ 2)。