动态编程:找到最大的三角形

时间:2015-02-27 09:26:19

标签: algorithm dynamic-programming pseudocode

我需要使用动态编程在零和一个矩阵中找到最大的三角形。所以如果这是我的矩阵:

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

然后在[2,2]和[4,4]处有两个最大的三角形,右角。我只需要寻找正确的等腰三角形(其角度为90°,45°,45°),我还需要只查看一个方向,因为所有其他方向都是对称的。所以基本上我需要一个函数,它接受矩阵并返回一个三角形,三角形是一个对象。我不需要完整的代码伪代码也很好。

首先我想到在这里使用方形算法:Dynamic programming - Largest square block,当你找到最大的方形时,那么最大的三角形必须在那里。但是我可以很容易地找到这些不起作用的反例。在那之后我试着看上面的单元格并用动态编程来计算它,但是我不知道下一步该做什么......所以我的计数看起来像上面的矩阵:

1 0 0 1 1
0 1 1 2 2
0 2 2 3 0
1 3 3 4 1
2 4 0 0 2

我认为必须以某种方式使用它。

更新

我认为我现在非常接近,当你遍历矩阵n * m并且使count [i] [j] = 1+ min(count [i-1] [j],count [i] [j -1]),所以看左边和上边的单元格。我们得到了这个:

1 0 0 1 1
0 1 1 2 2
0 1 2 3 0
1 2 3 4 1
1 2 0 0 1

这看起来对我很好,你可以看到[4,4]解决方案右角的位置。谁能想到任何反例?我只需要返回一个解决方案,所以返回这个解决方案很好。

更新2: 我找到了一个反例,让位置[4,4]为0,然后得到以下矩阵:

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

在遍历矩阵后,Count将如下所示:

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

现在它将返回右角[3,4](第三行第四列)的三角形,这是不正确的应该找到[2,2]。所以我想也许只是从左上角(我们到目前为止所做的)和右下角并从中获取最大值。因此右下角的计数将如下所示(查看下方和右侧的单元格):

1 0 0 2 1
0 4 3 2 1
0 3 2 1 0
2 2 1 1 1
1 1 0 0 1

现在我们找到[2,2]的解决方案。所以我认为使用这些方法会给我一个解决方案,任何人都可以想到一个更好的解决方案或一个反例吗?

更新3: kraskevich让我意识到我们必须使用这个算法四次。从左上角,右上角,左下角,右下角然后只取最大值,因为那时你已经采取了所有的可能性。谁有更好的方法来做到这一点?这个算法是否正确? (因此,只有相同算法的四倍,矩阵中只有另一个起点)

对于那些不太了解我在做什么的人(我可能会快一点)再看看这个:Dynamic programming - Largest square block这个方法非常相似,并且很好地解释了那里有什么。

3 个答案:

答案 0 :(得分:3)

是的,你的想法几乎是正确的。让我们把它正式化。

,根据权利要求:

f(i, j)是最大三角形的大小,右下角位于(i, j)位置,并且计算正确。

证明:

让我们使用归纳法。

基本情况:对于第一行和/或第一列中的所有单元格,三角形的大小为1或0(取决于此单元格中的值)。

归纳步骤:

我们假设已正确计算f(i - 1, j)f(i, j - 1)

  1. f(i - 1, j) >= f(i, j) - 1f(i, j - 1) >= f(i, j) - 1。就是这种情况,因为带有1 s的三角形的任何子三角形都是1 s的三角形。它意味着f(i, j) <= f(i - 1, j) + 1f(i, j) <= f(i, j - 1) + 1,或换句话说,f(i, j) <= min(f(i - 1, j) + 1, f(i, j - 1) + 1) = min(f(i - 1, j), f(i, j - 1)) + 1。因此,f(i, j) <= min(f(i - 1, j), f(i, j - 1)) + 1成立。

  2. 我们假设min(f(i - 1, j), f(i, j - 1)) = k。然后,k单元格中的大小为(i - 1, j)的三角形和k单元格中另一个大小为(i, j - 1)的三角形。这两个三角形与(i, j)单元格一起形成一个大小为k + 1的三角形。因此,f(i, j) >= min(f(i - 1, j), f(i, j - 1)) + 1

  3. 我刚刚展示了f(i, j) >= min(f(i - 1, j), f(i, j - 1)) + 1f(i, j) <= min(f(i - 1, j), f(i, j - 1)) + 1。它意味着f(i, j) = min(f(i - 1, j), f(i, j - 1)) + 1。因此,f(i, j)也正确计算。

    请注意,我们只考虑了在右下角位置有直角的三角形。为了考虑所有可能的三角形,我们可以简单地对给定矩阵的所有4种可能的旋转运行该算法。

    让我们证明在4个方向上运行此算法就足够了:

    矩阵中任意右等腰三角形的直角只有四个可能的位置:左下角,右下角,左上角和右上角。以固定方向运行此算法会找到此方向的最大三角形(我已经在上面证明了这一点)。因此,在所有可能的方向上运行该算法足以找到矩阵中的最大三角形。

    这个算法在时间复杂度方面是最优的,因为它具有O(n * m)时间复杂度,只是读取输入已经需要O(n * m)时间(如果没有看到矩阵的所有元素,我们找不到答案在一般情况下)。

答案 1 :(得分:0)

<强>声明

  

没有仅重述OP正在做的事情,事实上,我们更新计数的方式是不同的。

我同意@IVlad我应该详细说明为什么我的解决方案中所述的算法,所以我编辑了我的答案,包括关键想法(观察部分)我写的方法。

当我发布我的解决方案时,我完全没有意识到OP的方法(参见revision history for evidence, the third revision was made by me),所以实际上我甚至没有尝试重述OP正在做的事情。我只是希望为该问题提供 方法的形式化描述。

最初我一直试图避免发布正确的正确数学证明(因为Stackoverflow主要用于编码),我相信从我编写解决方案的方式来看,显然可以直接从归纳中导出正确性。尽管如此,我已根据OP的要求在最新更新中包含了完整性证明。

=============================================== =========================

<强>解决方案

您可以使用与您引用的帖子类似的方法。为简单起见,我们只考虑在讨论中在左上角找到具有直角的三角形。应该直截了当地概括我们对其他案例的处理方法。

我们定义一个与输入矩阵大小相同的矩阵D,我们将它的所有条目初始化为0。我们希望使用D[i][j]来表示(i, j)处的最大三角形(左上角有直角)的大小。

我们的方法基于以下观察:

  

对于n处大小为(i, j)的三角形,我们必须在n - 1和{{1}处都有三角形(至少)(i + 1, j) }。

我们可以利用这个事实来使用自下而上的方法来计算(i, j + 1)。我们将通过多次传递迭代计算D,在D - 次传递后,我们将找到所有大小为n的三角形。我们将根据以下规则更新n

  1. 在第一轮中,如果输入矩阵在D处有D[i][j] = 1,我们会设置1

  2. (i, j)次传递中,对于n中值D的每个条目(假设此条目位于n - 1,我们会检查两者是否(i, j) {1}}和D[i + 1][j]等于D[i][j+1],如果是,我们设置n - 1

  3. 当我们遇到没有D[i][j] = n条目更新的通行证时,我们会终止我们的算法。

  4. 备注: D - 次传递实际上找到了大小为n的三角形的所有位置,我们将使用此信息更新下一个传递,并且这是动态编程中的关键思想 - 我们已经找到了较小子问题的解决方案,我们将用它来帮助解决更大规模的问题。

    然后可以通过考虑n的最大条目来找到最大三角形的大小(以及它的位置)。

    正确性证明

    这可以通过数学归纳来实现。

    基本情况:如果位于D的大小为1的三角形,那么我们必须通过算法的步骤(1)(i, j)

    归纳步骤:假设我们找到了所有大小为D[i,j] = 1的三角形。对于任何大小为n - 1的三角形,从观察结果可以看出,它上面的三角形(即n处的角落)和底部(即(i + 1, j)处的角落)必须是大小为(i, j + 1)。通过我们的归纳假设,我们已经找到了这些三角形,因此通过我们算法的步骤(2),我们将得到n - 1

答案 2 :(得分:0)

此解决方案非常智能和优雅。我喜欢它!

但是,您可以稍微进行一些优化,因为以下断言并非完全正确:

  

对于(i,j)处的大小为n的三角形,我们必须在(i + 1,j)和(i,j + 1)处具有大小(至少)n-1的三角形。

这不是必要的条件。这是另一个(并且限制较少)的一个:

  

对于(i,j)的大小 n 的三角形,我们有一个大小三角形(至少) n - 1 < / em>在(i-1,j)的大小(至少) n - 1 位于(I,J)

这个结果在矩阵中的读数较少,因为很容易使用累加器来查找的大小。这导致读数降低约33%。

以下是该算法的可视化示例: http://jsfiddle.net/swvk32e8/1/

function solve() {
    var row, col;
    var i = 0;
    var n;
    var here,above;
    for (row = 1 ; row < SIZE ; row++) {
        n = grid[0 + SIZE * row];
        for (col = 1 ; col < SIZE ; col++) {
            here = grid[col + SIZE * row];
            if (here > 0) {
                if (n > 0) {
                    above = grid[col + SIZE * (row - 1)];
                    grid[col + SIZE * row] = 1 + Math.min(n, above);
                }
                n++;
            } else {
                n = 0;
            }
        }
    }
}