动态规划 - 最大方块
Posted
技术标签:
【中文标题】动态规划 - 最大方块【英文标题】:Dynamic programming - Largest square block 【发布时间】:2010-12-16 03:30:31 【问题描述】:我需要在一个包含 1 和 0 的巨大文件中找到最大的 1 正方形。我知道我必须使用动态编程。我将它存储在二维数组中。找到最大正方形的算法的任何帮助都会很棒,谢谢!
示例输入:
1 0 1 0 1 0
1 0 1 1 1 1
0 1 1 1 1 1
0 0 1 1 1 1
1 1 1 1 1 1
答案:
1 1 1 1
1 1 1 1
1 1 1 1
1 1 1 1
到目前为止我的代码:
int Square (Sq[int x][int y])
if (Sq[x][y]) == 0)
return 0;
else
return 1+MIN( Sq(X-1,Y), Sq(X,Y-1), Sq(X-1,Y-1) );
(假设值已经输入到数组中)
int main()
int Sq[5][6]; //5,6 = bottom right conner
int X = Square(Sq[5][6]);
我该如何从那里继续?
【问题讨论】:
请提供迄今为止您的发现的摘要。 输入也总是正方形的吗? @jeffamaphone:示例输入不是。从中得出你喜欢的结论;-) 嗯,我知道这与最长公共子字符串和 1-0 napsack 问题有关,但这就是我所得到的。没有输入可以是正方形或矩形。 任何想要将此问题作为练习的人都应该查看问题 E:来自东南地区 ACM ICPC 2010 问题集的最大平方:ser.cs.fit.edu/ser2010/problems 实施算法,并根据评委的输入对其进行测试/输出文件。 【参考方案1】:我想到的第一个算法是:
-
'&&' column/row 1 with column/row 2 if,也就是说,在每个条目与其在另一列/行中的对应条目之间进行'&&'操作。
检查结果列,如果有任何长度为 2 1,这意味着我们击中了一个 2x2 正方形。
下一列是前两列的结果。如果有任何长度为 3 1,我们就击中了一个 3x3 正方形。
重复直到所有列都用完。
从第 2 列开始重复 1-4。
我不会向您展示实现,因为它非常简单,而且您的问题听起来像是家庭作业。此外,可能还有更有效的方法可以做到这一点,因为如果输入非常大,这将变得很慢。
【讨论】:
【参考方案2】:好的,最低效但最简单的方法是:
选择第一项。检查是否为 1,如果是,则您有一个 1x1 正方形。
检查下方一个和一个向右,如果为 1,则检查第 2 列第 2 列,如果为 1,则为 2x2 正方形。
检查第 3 行第 1 列、第 2 列和第 3 列,加上第 1 行第 3 列、第 2 行第 3 列,如果为 1,则为 3x3。
所以基本上你不断扩展 row 和 col 并检查其边界内的所有单元格。一打到 0 就坏了,所以你连续移动 1 个点,然后重新开始。
在行尾,移动到下一行。
直到最后。
您可能会看到它们如何适合 while 循环等,以及如何使用 &&
s 来检查 0,当您查看它时,您可能还会注意到它是如何被加速的。但正如刚才提到的另一个答案,它听起来确实有点像家庭作业,所以我们将把实际代码留给你。
祝你好运!
【讨论】:
【参考方案3】:设输入矩阵为M
: n x m
T[i][j]
是 DP 矩阵,其中包含最大正方形边和正方形底部右角 (i,j)
。
填表的一般规则:
if (M[i][j] == 1)
int v = min(T[i][j-1], T[i-1][j]);
v = min(v, T[i-1][j-1]);
T[i][j] = v + 1;
else
T[i][j] = 0;
结果方块大小是T
中的最大值。
填写T[i][0]
和T[0][j]
很简单。
我不确定这个算法是否可以用于您的巨大文件,
但您不需要存储整个矩阵T
,只需要存储当前行和上一行。
以下注释有助于理解一般概念:
所有具有右下角 (i-1, j)、(i, j-1)、(i-1, j-1) 且大小为 s 的正方形都在具有右下角 (i, j) 的正方形的内部大小为 s+1。 如果有大小为 s+1 且右下角位于 (i, j) 的正方形,则最大正方形的大小为右下角 (i-1, j), (i, j-1), (i -1, j-1) 至少为 s。 反之亦然。如果至少一个在 (i-1, j), (i, j-1), (i-1, j-1) 处有下直角的正方形的大小小于 s,则右下角正方形的大小at (i, j) 不能大于 s+1。【讨论】:
感谢您的帮助,但是“结果端”和填充 T[i][0] 和 T[0][i] 是什么意思?有什么方法可以通过更快的方式与您取得联系? 结果方块的大小等于 T 中的最大值。 这个简单公式背后的逻辑是什么? 我已经添加了一些澄清来回答。希望他们有帮助【参考方案4】:这是解决方案的草图:
对于每个单元格,我们将记录使用该单元格作为左上角可以制作多大的正方形。显然,所有为 0 的单元格都将 0 作为计数。
从右下角的单元格开始迭代,到左下角,然后向上一行重复。
在每次扫描时执行以下操作:
-
如果单元格有0分配
count=0
如果单元格有 1 并且是边缘单元格(仅底部或右侧边缘),则分配 count=1
对于所有其他单元格,检查其右侧、右下方和下方的单元格计数。取其中的最小值并加 1 并将其分配给计数。保留一个全局 max_count
变量以跟踪迄今为止的最大计数。
在遍历矩阵结束时,max_count
将具有所需的值。
复杂性不超过矩阵遍历的成本。
这是遍历后矩阵的样子。括号中的值是计数,即使用左上角的单元格可以组成的最大正方形。
1(1) 0(0) 1(1) 0(0) 1(1) 0(0)
1(1) 0(0) 1(4) 1(3) 1(2) 1(1)
0(0) 1(1) 1(3) 1(3) 1(2) 1(1)
0(0) 0(0) 1(2) 1(2) 1(2) 1(1)
1(1) 1(1) 1(1) 1(1) 1(1) 1(1)
在 Python 中的实现
def max_size(mat, ZERO=0):
"""Find the largest square of ZERO's in the matrix `mat`."""
nrows, ncols = len(mat), (len(mat[0]) if mat else 0)
if not (nrows and ncols): return 0 # empty matrix or rows
counts = [[0]*ncols for _ in xrange(nrows)]
for i in reversed(xrange(nrows)): # for each row
assert len(mat[i]) == ncols # matrix must be rectangular
for j in reversed(xrange(ncols)): # for each element in the row
if mat[i][j] != ZERO:
counts[i][j] = (1 + min(
counts[i][j+1], # east
counts[i+1][j], # south
counts[i+1][j+1] # south-east
)) if i < (nrows - 1) and j < (ncols - 1) else 1 # edges
return max(c for rows in counts for c in rows)
【讨论】:
+ 尽管它是一个相互竞争的答案,但你的答案在复杂性方面显然是最佳的,非常巧妙! 也许一件事,第 2 点说如果它是一个边缘单元格,只需分配 1,这仅适用于底部/右侧边缘单元格,因为左/顶部边缘单元格可能是较大的左上角正方形? 我的错,我们必须检查一下左边缘和上边缘的边缘单元格,让我编辑我的解决方案。非常感谢! 非常简单且完全正确。我希望我可以多次投票。 为什么你从右下角开始而不是(像往常一样)从左上角开始?结果是一样的,只是递归看起来更自然(因为它将使用递增索引,并且基本情况是 0 而不是 n)。 – 除此之外,完美的答案。【参考方案5】:LSBRA(X,Y)
表示“X,Y 右下角的最大正方形”
伪代码:
LSBRA(X,Y):
if (x,y) == 0:
0
else:
1+MIN( LSBRA(X-1,Y), LSBRA(X,Y-1), LSBRA(X-1,Y-1) )
(对于边缘单元格,您可以跳过 MIN 部分,如果 (x,y) 不为 0,则返回 1。)
在“波浪”中沿对角线穿过网格,如下所示:
0 1 2 3 4
+----------
0 | 1 2 3 4 5
1 | 2 3 4 5 6
2 | 3 4 5 6 7
3 | 4 5 6 7 8
或者,只要您填写边缘单元格,就可以从左到右、从上到下进行操作。
0 1 2 3 4
+----------
0 | 1 2 3 4 5
1 | 6 7 8 9 .
2 | . . . . .
3 | . . . . .
这样你就永远不会遇到之前没有计算过必要数据的计算——所以所有LSBRA()
“调用”实际上只是你之前计算结果的表查找(因此动态编程方面)。
为什么会起作用
为了在 X,Y 处有一个右下角的正方形 - 它必须包含与其他 3 个角中的每一个相接触的少一维的重叠正方形。换句话说,拥有
XXXX
XXXX
XXXX
XXXX
你还必须...
XXX. .XXX .... ....
XXX. .XXX XXX. ....
XXX. .XXX XXX. ....
.... .... XXX. ...X
只要你有这 3 个(每个 LSBRA 检查)N 大小的方块加上当前方块也被“占用”,你就会有一个 (N+1) 大小的方块。
【讨论】:
对不起,你能解释一下伪代码吗? LSBRA是一个函数,返回一个整数(最大值?),min返回传入的3个LSBRA中的最小值? LSBRA 只是“计算此值”的占位符。对于动态编程实现,它基本上意味着“存储在我们的 X,Y 结果数组中的内容”。对于递归实现,它将是一个函数。是的,MIN() 意味着取最小的参数。 我用您的解决方案编辑了我的原始帖子,但它似乎是错误的。你能看一下吗? =]【参考方案6】:设 N 为二维数组中的单元数。存在一种非常有效的算法来列出所有最大的空矩形。最大的空正方形位于这些空矩形之一内,一旦计算出最大空矩形的列表,创建它就很简单了。可以在www.ulg.ac.be/telecom/rectangles 找到一篇介绍 O(N) 算法来创建此类列表的论文以及源代码(未优化)。请注意,存在一个证明(见论文),最大空矩形的数量以 N 为界。因此,选择最大的空正方形可以在 O(N) 内完成,总体方法也是 O(N)。在实践中,这种方法非常快。实现非常容易,因为整个代码不应超过 40 行 C 代码(列出所有最大空矩形的算法大约需要 30 行 C 代码)。
【讨论】:
【参考方案7】:这里的关键是您可以使用动态编程来跟踪区域的根,而不是实际区域。
算法如下:
存储一个称为 max-square 的二维整数数组,其中索引 i,j 处的元素表示它所在的正方形的大小,其中 i,j 是右下角。 (如果 max[i,j] = 2,则表示索引 i,j 是大小为 2^2 = 4 的正方形的右下角)
对于每个索引 i,j:
如果在 i,j 元素为 0,则将 max-square i,j 设置为 0。
其他:
求 max-square[i - 1, j] 和 max-square[i, j - 1] 和 max-square[i - 1][j -1] 的最小值。将 max-square[i, j] 设置为 1 + 3 的最小值。归纳起来,您最终将填充 max-square 数组。查找/或跟踪过程中的最大值,返回该值^2。
看看人们提出的这些解决方案: https://leetcode.com/discuss/questions/oj/maximal-square?sort=votes
【讨论】:
以上是关于动态规划 - 最大方块的主要内容,如果未能解决你的问题,请参考以下文章