我需要使用动态编程在零和一个矩阵中找到最大的三角形。所以如果这是我的矩阵:
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这个方法非常相似,并且很好地解释了那里有什么。
答案 0 :(得分:3)
是的,你的想法几乎是正确的。让我们把它正式化。
,根据权利要求:
f(i, j)
是最大三角形的大小,右下角位于(i, j)
位置,并且计算正确。
证明:
让我们使用归纳法。
基本情况:对于第一行和/或第一列中的所有单元格,三角形的大小为1或0(取决于此单元格中的值)。
归纳步骤:
我们假设已正确计算f(i - 1, j)
和f(i, j - 1)
。
f(i - 1, j) >= f(i, j) - 1
和f(i, j - 1) >= f(i, j) - 1
。就是这种情况,因为带有1
s的三角形的任何子三角形都是1
s的三角形。它意味着f(i, j) <= f(i - 1, j) + 1
和f(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
成立。
我们假设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
。
我刚刚展示了f(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) = 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
:
在第一轮中,如果输入矩阵在D
处有D[i][j] = 1
,我们会设置1
。
在(i, j)
次传递中,对于n
中值D
的每个条目(假设此条目位于n - 1
,我们会检查两者是否(i, j)
{1}}和D[i + 1][j]
等于D[i][j+1]
,如果是,我们设置n - 1
。
当我们遇到没有D[i][j] = n
条目更新的通行证时,我们会终止我们的算法。
备注: 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;
}
}
}
}