最有效的算法是在二维图中找到最大的正方形

时间:2013-12-02 18:40:13

标签: algorithm

我想知道不同的算法,找到点缀着障碍物的二维地图中的最大正方形。

一个例子,其中o将成为障碍:

...........................
....o......................
............o..............
...........................
....o......................
...............o...........
...........................
......o..............o.....
..o.......o................

最大的广场将是(如果我们选择第一个广场):

.....xxxxxxx...............
....oxxxxxxx...............
.....xxxxxxxo..............
.....xxxxxxx...............
....oxxxxxxx...............
.....xxxxxxx...o...........
.....xxxxxxx...............
......o..............o.....
..o.......o................

找到它的最快算法是什么?复杂度最小的那个?

编辑:我知道人们对接受的答案中解释的算法感兴趣,所以我制作了一个文档来解释它,你可以在这里找到它:

https://docs.google.com/document/d/19pHCD433tYsvAor0WObxa2qusAjKdx96kaf3z5I8XT8/edit?usp=sharing

5 个答案:

答案 0 :(得分:7)

以下是如何在最佳量时间内执行此操作,O(nm)。这是建立在@dukeling的洞察力之上,您永远不需要检查尺寸小于当前已知最佳解决方案的解决方案。

关键是能够构建一个可以在O(1)时间内回答此查询的数据结构。

  • 广场的左上角是否为r,c并且大小为k?
  • 是否存在障碍物

要解决这个问题,我们将支持回答一个稍微更难的问题,也在O(1)中。

  • 矩形中的项目数是多少,从r1,c1到r2,c2?

通过矩形计数问题的答案,很容易回答方形存在问题。

要回答矩形计数问题,请注意,如果您已经预先计算了左上角开始的每个矩形的答案,那么您可以通过某种方式回答从r1,c1到r2,c2的一般问题。仅使用从左上角开始的矩形的聪明/包含排除策略

              c1   c2  
-----------------------
|             |    |  |
|   A         | B  |  |
|_____________|____|  |  r1
|             |    |  |
|    C        |  D |  |
|_____________|____|  |  r2
|_____________________|

我们想要D里面的东西计数。就我们从左上角预先计算的数量来说。

Count(D) = Count(A ∪ B ∪ C ∪ D) - Count(A ∪ C) - Count(A ∪ B) + Count(A)

你可以通过做一些聪明的行/列部分和来预先计算O(nm)中的所有左上角矩形,但我会把它留给你。

然后回答您想要解决的问题,只需检查可能的解决方案,从至少与您最熟悉的解决方案一样的解决方案开始。你知道的最好只会达到最小(n,m)次总数,所以best_possible增量很少发生,几乎所有的方格都会在O(1)时间内被拒绝。

best_possible = 0
for r in range(n):
 for c in range(m):
   while True:                      
     # this looks O(min(n, m)), but it's amortized O(1) since best_possible
     # rarely increased.      
     if possible(r, c, best_possible+1):
       best_possible += 1
     else:
       break

答案 1 :(得分:5)

一个想法,利用二分搜索。

基本理念:

从左上角开始。看看1x1方格是否可行。

  • 如果可行,请将方块的边长增加1并重复。
  • 如果不起作用,请向右移动并重复。如果您已到达最右侧位置,请移至下一行。

原生方法:

我们可以简单地检查每个步骤中每个方块的每个可能的单元格,但这是相当低效的。

优化方法:

当增加平方大小时,我们可以在下一行和列上进行二进制搜索,以查看该行/列是否包含任何位置的障碍物。

当向右移动时,我们可以对每个下一列进行二元搜索,以确定该列是否包含任何位置的障碍物。

向下移动时,我们可以在目标位置的每一列上执行类似的二进制文件。

实施说明:

首先,我们需要遍历所有行和列,并设置包含每个行的障碍位置的数组,我们可以将其用于二进制搜索。

运行时间:

我们进行2次二进制搜索以增加方块大小,并且方块大小最大化网格的大小,因此相当小(O(min(m,n) log max(m,n)))并由下面的主导。

除此之外,对于每个位置,我们对列进行单个二进制搜索。

因此,对于包含m列和n行的网格,整体复杂度为O(mn log m)

但请注意,当网格稀疏时,我们实际上在下面搜索的内容很少。

示例:

对于你的例子:

 012345678901234567890123456
0...........................
1....o......................
2............o..............
3...........................
4....o......................
5...............o...........
6...........................
7......o..............o.....
8..o.......o................

我们首先在左上角尝试一个1x1的方格,这样可行。

然后是2x2平方。为此,我们对第1行的范围[0,1]进行二分搜索,该范围可以简单地用{4}表示 - 一个与障碍物所在位置对应的单个位置的数组。我们还对第1列的范围[0,1]进行二分搜索,该范围不包含任何障碍,因此是一个空数组 - {}

然后是3x3平方。为此,我们在第2行对[0,2]进行二分搜索,其中包含位置12处的1个障碍,因此{12}。我们还对第2列的[0,2]进行二分搜索,其中包含位置8的障碍,因此{8}

然后一个4x4平方。为此,我们在第3行[0,3]上对{}进行二分搜索。对于第3列的[0,3] - {}

然后是5x5平方。为此,我们在第4行[0,4]上对{4}进行二分搜索。对于[0,4]第4列 - {1,4}

这是我们实际找到的第一个。在[0,4]范围内,我们在行和列中都找到4(我们只需要找到其中一个)。所以这表明失败了。

从这里开始,[0,4]对第4列进行二元搜索(再次 - 不是必需的)。然后找到[0,4]的二进制搜索列5-8,找不到任何一个,因此从位置5,0开始的方格是下一个可能的候选者。

所以从这里我们尝试将平方尺寸增加到5x5,然后工作,然后是6x6和7x7,这是有效的。

然后我们尝试8x8,这不起作用。

等等。

我知道二进制搜索,但你的工作方式是什么?

所以我们基本上在一组值中进行范围搜索。这很容易做到。首先搜索范围的起始值,然后搜索结束值。如果我们达到相同的点,则范围内没有值。

我们并不关心范围内存在哪些值,只是有没有。

答案 2 :(得分:2)

所以这是一个粗略的方法。

Store the x-y positions of all the obstacles.
For each obstacle O
   find obstacle C that is nearest to it column-wise.
   find obstacle R-top that is nearest to it row-wise from the top.
   find obstacle R-bottom that is nearest to it row-wise from the bottom.
   if (|R-top.y - R-bottom.y| != |O.x - C.x|) continue
   Size of the square = Abs((R-top.y - R-bottom.y) * (O.x - C.x))
Keep track of the sizes and positions to find the largest square

复杂度大约是O(k^2),其中k是障碍物的数量。如果使用二分查找,可以将其减少到O(k * log k)

答案 3 :(得分:2)

以下SO文章与您尝试解决的问题相同/相似。您可能希望查看这些答案以及对您问题的回答。

这是我使用的基线案例,用简化的Python /伪代码编写。

# obstacleMap is a list of list of MapElements, stored in row-major order
max([find_largest_rect(obstacleMap, element) for row in obstacleMap for element in row])    

def find_largest_rect(obstacleMap, upper_left_elem):    
    size = 0    
    while not has_obstacles(obstacleMap, upper_left_elem, size+1):    
        size += 1    
    return size    

def has_obstacles(obstacleMap, upper_left_elem, size):    
    #determines if there are obstacles on the on outside square layer    
    #for example, if U is the upper left element and size=3, then has_obstacles checks the elements marked p.    
    # .....    
    # ..U.p    
    # ....p    
    # ..ppp    
    periphery_row = obstacleMap[upper_left_elem.row][upper_left_elem.col:upper_left_elem.col+size]    
    periphery_col = [row[upper_left_elem.col+size] for row in obstacleMap[upper_left_elem.row:upper_left_elem.row+size]    
    return any(is_obstacle(elem) for elem in periphery_row + periphery_col)

def is_obstacle(elem):    
    return elem.value == 'o'    

class MapElement(object):    
    def __init__(self, row, col, value):    
        self.row = row    
        self.col = col    
        self.value = value    

答案 4 :(得分:0)

这是一种使用递归关系的方法: -

isSquare(R,C1,C2) = noObstacle(R,C1,R,C2) && noObstacle(R,C2,R-(C2-C1),C2) && isSquare(R-1,C1,C2-1)

isSquare(R,C1,C2) = square that has bottom side (R,C1) to (R,C2) 

noObstacle(R1,C1,R2,C2) = checks whether there is no obstacle in line segment (R1,C1) to (R2,C2)

Find Max (C2-C1+1) which where isSquare(R,C1,C2) = true  

您可以使用动态编程在多项式时间内解决此问题。使用合适的数据结构来搜索障碍物。