在具有1和0的矩阵中查找所有1的最大尺寸子矩阵

时间:2010-09-27 18:03:27

标签: algorithm dynamic-programming

假设给你一个mXn位图,由一个数组M [1..m,1 .. n]表示,其数据全部为0或1.一个全部的块是M [i的形式]的子数组。 .i0,j .. j0]其中每个位等于1.描述和分析一个有效的算法,找到M中具有最大面积的全一块

我正在尝试制作动态编程解决方案。但是我的递归算法在O(n ^ n)时间内运行,即使在记忆之后我也想不到将其降低到O(n ^ 4)以下。有人可以帮我找到更有效的解决方案吗?

4 个答案:

答案 0 :(得分:24)

O(N)(元素数)解决方案:

A
1 1 0 0 1 0
0 1 1 1 1 1
1 1 1 1 1 0
0 0 1 1 0 0 

生成一个数组C,其中每个元素代表上面包括它的1的数量,直到第一个0。

C
1 1 0 0 1 0
0 2 1 1 2 1
1 3 2 2 3 0
0 0 3 3 0 0 

我们希望找到最大化R的行l和左,右索引r(r-l+1)*min(C[R][l..r])。这是一个在O(cols)时间内检查每一行的算法:

维护一对(h, i),其中C[R][i-1] < h ≤ C[R][i]。在任何位置cur,对于堆栈中的所有对h=min(C[R][i..cur]),我们应该(h, i)

对于每个元素:

  • 如果h_cur>h_top
    • 推送(h, i)
  • 否则:
    • h_cur<h_top
      • 弹出堆栈顶部。
      • 检查它是否会成为新的最佳状态,即(i_cur-i_pop)*h_pop > best
    • 如果h_cur>h_top
      • 推送(h, i_lastpopped)

我们示例中第三行的执行示例:

  i =0      1      2      3      4      5
C[i]=1      3      2      2      3      0
                                 (3, 4)
 S=         (3, 1) (2, 1) (2, 1) (2, 1)
     (1, 0) (1, 0) (1, 0) (1, 0) (1, 0)    
     (0,-1) (0,-1) (0,-1) (0,-1) (0,-1) (0,-1) 

i=0, C[i]=1)推(1, 0)  i=1, C[i]=3)推(3, 1)
 i=2, C[i]=2)弹出(3, 1)。检查(2-1)*3=3是否是新的最佳  弹出的最后i为1,因此请(2, 1)  i=3, C[i]=2h_cur=h_top所以什么都不做  i=4, C[i]=3)推(3, 4)
 i=5, C[i]=0)弹出(3, 4)。检查(5-4)*3=3是否是新的最佳  流行(2, 1)。检查(5-1)*2=8是否是新的最佳  流行(1, 0)。检查(5-0)*1=5是否是新的最佳  结束。 (好的,我们应该在最后添加一个额外的术语C [cols] = 0以获得良好的衡量标准)。

答案 1 :(得分:9)

这是一个O(numCols*numLines^2)算法。让S[i][j] = sum of the first i elements of column j.

我将在这个例子中使用算法:

M
1 1 0 0 1 0
0 1 1 1 0 1
1 1 1 1 0 0
0 0 1 1 0 0 

我们有:

S
1 1 0 0 1 0
1 2 1 1 1 1
2 3 2 2 1 1 
2 3 3 3 1 1

现在考虑在一维数组中找到所有1的最大子数组的问题。这可以使用这个简单的算法来解决:

append 0 to the end of your array
max = 0, temp = 0
for i = 1 to array.size do
  if array[i] = 1 then
    ++temp
  else
    if temp > max then
      max = temp
    temp = 0

例如,如果你有这个1d数组:

1 2 3 4 5 6
1 1 0 1 1 1
你会这样做:

首先添加0

1 2 3 4 5 6 7
1 1 0 1 1 1 0

现在,请注意,无论何时点击0,您都知道连续序列的结束位置。因此,如果保持当前1的运行总数(temp变量),则可以在达到零时将该总数与到目前为止的最大值(max变量)进行比较,然后重置跑步总数。这将为变量max中的连续序列1提供最大长度。

现在您可以使用此子算法查找问题的解决方案。首先在您的矩阵中添加0列。然后计算S

然后:

max = 0
for i = 1 to M.numLines do
  for j = i to M.numLines do
    temp = 0
    for k = 1 to M.numCols do
      if S[j][k] - S[i-1][k] = j - i + 1 then
        temp += j - i + 1
      else
        if temp > max then
          max = temp
        temp = 0

基本上,对于子阵列的每个可能高度(有O(numLines^2)个可能的高度),您可以通过应用一维数组的算法找到具有该高度的最大区域的高度(在{{1}中) })。

考虑以下“图片”:

O(numCols)

这意味着我们已修复高度 M 1 1 0 0 1 0 0 i 0 1 1 1 0 1 0 j 1 1 1 1 0 0 0 0 0 1 1 0 0 0 。现在,将j - i + 1i之间的所有矩阵元素包含在内:

j

请注意,这类似于一维问题。让我们对列进行求和,看看我们得到了什么:

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

现在,问题被简化为一维情况,但我们必须找到连续1 2 2 2 0 1 0 (在这种情况下为j - i + 1)值的子序列。这意味着我们2“窗口”中的每一列都必须包含一列。我们可以使用j - i + 1矩阵有效地检查这一点。

要了解S的工作原理,请再次考虑一维案例:让S。那么子序列s[i] = sum of the first i elements of the vector a的总和是多少?它是包括a[i..j]在内的所有元素的总和,减去包括a[j]在内的所有元素的总和,即a[i-1]。除了每列有s[j] - s[i-1]之外,第二种情况的作用相同。

我希望这很清楚,如果您还有其他问题,请询问。

我不知道这是否符合您的需求,但我认为还有基于动态编程的s算法。我还想不通,除了你所追求的子阵是正方形的情况。然而,有人可能会有更好的见解,所以再等一下。

答案 2 :(得分:1)

定义一个新矩阵A将存储在A [i,j]中的两个值:最大子矩阵的宽度和高度,左上角位于i,j,从右下角开始填充此矩阵,按行从下到上。你会发现四种情况: 当给定矩阵在[i,j] = 1

时执行这些情况

情况1:原始矩阵中的右或底相邻元素都不等于当前矩阵,即:M [i,j]!= M [i + 1,j]和M [i,j] != M [i,j + 1]是原始矩阵的M,在这种情况下,A [i,j]的值是1x1

情况2:右边的邻居元素等于当前的邻居元素,但是底部的邻居元素不同,A [i,j] .width的值是A [i + 1,j] .width + 1和A [I,J] .height = 1

情况3:底部的邻居元素相等但右边的元素不同,A [i,j] .width = 1,A [i,j] .height = A [i,j + 1]。高度+ 1

案例4:两个邻居都是平等的:         考虑三个矩形:

  1. A [I,J] .WIDTH = A [I,J + 1] .WIDTH + 1; A [I,J] .height = 1;

  2. A [I,J] .height = A [1 + 1,j]的.height + 1;一个[I,J] .WIDTH = 1;

  3. A [i,j] .width = min(A [i + 1,j] .width + 1,A [i,j + 1] .width)和A [i,j]。高度= min(A [i,j + 1] + 1,A [i + 1,j])

  4. 在上述三种情况下具有最大面积的那一个将被视为代表该位置的矩形。

    在i,j左上角的最大矩阵的大小是A [i,j] .width * A [i,j]。高,所以你可以更新计算A时找到的最大值[ I,J]

    底部行和最右边的列元素被视为它们的底部和右边的邻居分别不同。

答案 3 :(得分:0)

这是C#中的O(N)实现。

我们的想法是使用动态编程来构建一个累积的矩阵,其大小最大的子矩阵包括当前的单元本身。

    public static int LargestSquareMatrixOfOne(int[,] original_mat)
    {
        int[,] AccumulatedMatrix = new int[original_mat.GetLength(0), original_mat.GetLength(1)];
        AccumulatedMatrix[0, 0] = original_mat[0, 0];
        int biggestSize = 1;
        for (int i = 0; i < original_mat.GetLength(0); i++)
        {
            for (int j = 0; j < original_mat.GetLength(1); j++)
            {
                if (i > 0 && j > 0)
                {
                    if (original_mat[i, j] == 1)
                    {
                        AccumulatedMatrix[i, j] = Math.Min(AccumulatedMatrix[i - 1, j - 1], (Math.Min(AccumulatedMatrix[i - 1, j], AccumulatedMatrix[i, j - 1]))) + 1;
                        if (AccumulatedMatrix[i, j] > biggestSize)
                        {
                            biggestSize = AccumulatedMatrix[i, j];
                        }
                    }
                    else
                    {
                        AccumulatedMatrix[i, j] = 0;
                    }
                }
                else if ( (i > 0 && j == 0) || (j > 0 && i == 0))
                {
                    if (original_mat[i, j] == 1) { AccumulatedMatrix[i, j] = 1; }
                    else { AccumulatedMatrix[i, j] = 0; }
                }
            }
        }
        return biggestSize;
    }