棋盘游戏:找到带有限制的红色点的最大绿色点

时间:2019-07-01 14:27:32

标签: python performance numpy optimization

场景:

  • 在MxN大小的棋盘上,有红色和绿色的块。

  • 板上的每个正方形可以包含任意数量的任何颜色的块。 (我们可以在同一个正方形中有5个-3个绿色和2个红色,例如,绿色,红色或红色,或任意数字)

  • 我正在寻找板上尽可能多的绿色碎片的轴对齐矩形。

  • 但是,矩形所包含的红色块的数量不得超过给定数量。

  • 矩形的一个角必须为(0,0)。

示例:

电路板尺寸为6x4,红色部分标记为“ x”,绿色部分标记为“ o”。

      +-+-+-+-+-+-+
    3 | | | |o|x|o|
      +-+-+-+-+-+-+
    2 |o| |x| | |o|
      +-+-+-+-+-+-+
    1 |o| |o| |o|x|
      +-+-+-+-+-+-+
    0 | |o| |x| |x|
      +-+-+-+-+-+-+
       0 1 2 3 4 5
  • 如果我们允许2个红色小块,那么(4,2)是一个最佳解决方案,因为(0,0)和(4,2)之间的区域包含5个绿色小块和2个红色小块。 最多有2个红色块的点最多包含5个绿色块。 (3,3)也是一个最佳解决方案。

  • 如果我们允许3个红色小块,那么(4,3)是唯一的最佳解决方案,有6个绿色小块。

我得到了

  • 木板尺寸
  • 所有绿色和红色部分的坐标,
  • 允许的红色碎片数(“ maxRed”)

目标: 对于任何给定的“ maxRed”,该类应能够计算坐标(x,y),以使(0,0)和(x,y)之间的轴对齐矩形最多包含“ maxRed”个红色片断,并且绿色块的数量最大。

我的问题:

通过遍历所有具有最大绿色点和给定最大红色点的可能矩阵(以找到最大的三角形)来解决此问题显然效率低下,我正在尝试找到一种方法,无需使用蛮力

一些直觉:

我想看一下从原点(0,0)起,允许在矩形内的壁橱最大红色点(如果maxRed = 2,则最接近两个红色点),然后检查所有可能的“扩展”从该点开始的矩形(仅宽度,高度,宽度和高度,等等。)的效率也不是我所相信的(在最坏的情况下它效率不高)。

然后我想也许maxRed等于2并找到最接近的两个红点,那么我们可以检查第三个maxRed在哪里(如果不存在,则将整个矩阵作为矩形返回),然后以某种方式搜索'较少的选项数量-仍然需要考虑所有情况(第三个点可以在当前矩形的顶部,或者从其左侧,或者对角线),以及是否来自例如在它的顶部,那么可能会有一个情况,我可以将其宽度扩展-也许不能(因为它的右边还有另一个红点)。但您明白了,以某种方式找到了一种并非蛮力且尽可能高效的算法。

问题2:另外一个我想知道如何解决的有趣问题: 如果坐标定义为“浮动”,这意味着该板没有网格,您将如何解决该问题。现在,您需要返回最佳浮点(x,y)坐标,以使(0,0)和(x,y)之间的区域最多包含“ maxRed”红色块,而绿色块的数目最大。您将如何找到它?复杂程度如何?

案例1,例如: enter image description here

情况2: enter image description here

另一种直观的认识:

边缘情况:如果地图中的红点小于2,则返回所有矩阵的矩形。

  1. 然后,我们首先创建覆盖壁橱的两个红色点的矩形。 (例如,我们的矩形将扩展到y = 3,x = 2)覆盖了所有区域。

  2. 然后我们检查红色点的壁橱y轴大于当前矩形的y(y = 3),红色点的壁橱x轴又大于当前矩形的y轴矩形的x(x = 2),壁橱x和y也可以来自相同的第三个红色点,而不必来自两个不同的红色点。

  3. 这样,我们可以看到矩形可以延伸多远。

  4. 然后,在第3步中,如果可能,我们将尝试迭代上移(i + 1),并检查j的所有扩展,然后转到i + 2并检查所有j ..

4.1然后在可能的情况下右移(j + 1),并检查所有i,然后继续进行j + 2,依此类推。

并返回我们可以构建的最高矩形-并且在此过程中,我们不会检查太多的i和j。

但这还不够,

因为像“案例2”中那样存在边缘情况

在同一位置有2个红点,因此我们必须仔细检查第二远的红点(如果x或y或两者都比第一个壁橱的红点大得多)另一个红点,如果它在同一单元格中总共有两个红点,则我们将其扩展到x或y-并从那里继续向上/向下扩展。

(我们可以看到它是对角线,还是从右边还是从顶部),如果它是从第一个红点的右边(意味着x大于我们当前的x-仅在x轴上),那么我们可以检查我们可以延伸到顶部的距离-通过查看红色点列表(如果我们在顶部有红色点),如果没有,那么我们会一直延伸到终点,如果第二个红色点在我们顶部,则使用相同的方法,我们可以检查向右延伸多远。

,如果第二个红点在我们的对角线上(如用法示例中所示),那么我们将检查仅可以向左延伸多远,以及仅可以向顶部延伸多远,然后看看有什么对我们更好寻找更多的绿点。

我猜这种解决方案应该可以工作,并给出O(1)时间复杂度。

3 个答案:

答案 0 :(得分:7)

如果您认为只有两个整数变量ij0 <= i <= M, 0 <= j <= N,则可以使用动态编程来解决。在没有LaTeX引擎的情况下,我会尽量清楚地编写此代码,请耐心等待。

假设您创建四个M * N整数GRVL矩阵。对于每个点(i, j)g_ij表示该正方形上的绿色块数,r_ij表示红色块数。 v_ij表示矩形(0, 0) - (i, j)中的绿色块数,如果红色块的数量过多,则为0,而l_ij表示矩形块中的红色块数(无穷大)如果原始值将超过限制。如果我说的是一个单元格的值,我的意思是v_ij,则一个单元格的限制等于l_ij

从点(0, 0)开始,编程方法如下:

  1. 给出点(i, j)
  2. 可能的行进路线为(i + 1, j),向右为(i, j + 1)
  3. 对于(i + i, j),矩形l_(i + 1)j中红色部分的数量等于前一个单元格l_ij的限制+上方单元格中红色部分的数量矩形,因此从(0, j)(i + 1, j)的单元格。如果您使用限制l_ij,则不必为一个步骤计算(i + 1) * j个单元格的总和,而只需计算j + 1个单元格中的j个单元格的总和该行加上一个存储的值。计算v_(i + 1)j的方法也一样,只需使用存储的值v_ij加上上一行中绿色的数量即可。
  4. 如果超过了限制的红色部分,则可以在(i + 1, j)和右上角(M, N)之间创建一个矩形,并指定所有这些单元格的极限,因为所有可能的矩形可以形成的矩形必须包含矩形(0, 0) - (i + 1, j),因此它们必须包含太多红色块。正确的计算非常相似。
  5. 一旦没有更多未知的片段,只需在O(MN)时间中找到V的最高值即可。

对于第二个问题,可能的近似值是在0到1之间采用一个步长大小,然后将所有值除以该步长,然后向下舍入,因此(2/3, 7/5)步长为1/10成为(6, 14)。然后使用这些步骤应用算法。您可以多次运行,减小步长,在两次运行之间存储和转换矩阵V,因此可以避免进行大量计算。希望对您有所帮助!

更新:现在带有示例执行

例如,在每个单元格(x, y)中分别表示绿色和红色的数量。矩阵G和R旁边是-空值表示0。 红件的数量限制为3。

                                       G                   R
  +-----+-----+-----+-----+    +---+---+---+---+   +---+---+---+---+ 
3 |(1,2)|     |     |(4,0)|  3 | 1 |   |   | 4 | 3 | 2 |   |   |   | 
  +-----+-----+-----+-----+    +---+---+---+---+   +---+---+---+---+ 
2 |     |(3,1)|     |(1,2)|  2 |   | 3 |   | 1 | 2 |   | 1 |   | 2 | 
  +-----+-----+-----+-----+    +---+---+---+---+   +---+---+---+---+ 
1 |(1,1)|(1,1)|     |     |  1 | 1 | 1 |   |   | 1 | 1 | 1 |   |   | 
  +-----+-----+-----+-----+    +---+---+---+---+   +---+---+---+---+ 
0 |     |(0,1)|(3,1)|(1,1)|  0 |   |   | 3 | 1 | 0 |   | 1 | 1 | 1 | 
  +-----+-----+-----+-----+    +---+---+---+---+   +---+---+---+---+ 
     0     1     2     3         0   1   2   3       0   1   2   3   

(0, 0)开始,我们有0个红色块和0个绿色块,因此VL如下所示:

          V                   L         
  +---+---+---+---+   +---+---+---+---+ 
3 |   |   |   |   | 3 |   |   |   |   | 
  +---+---+---+---+   +---+---+---+---+ 
2 |   |   |   |   | 2 |   |   |   |   | 
  +---+---+---+---+   +---+---+---+---+ 
1 |   |   |   |   | 1 |   |   |   |   | 
  +---+---+---+---+   +---+---+---+---+ 
0 | 0 |   |   |   | 0 | 0 |   |   |   | 
  +---+---+---+---+   +---+---+---+---+ 
    0   1   2   3       0   1   2   3    

出于完整性考虑,我确实在VL中填充了零值。 现在我们可以上到(1, 0),然后上到(0, 1)。向上,我们得到l_10 = l_00 + r_10 = 0 + 1 = 1v_10 = v_00 + g_10 = 0 + 1 = 1。在右侧,我们得到l_01 = l_00 + r_01 = 0 + 1 = 1v_01 = v_00 + g_01 = 0。这给我们以下情况:

          V                   L         
  +---+---+---+---+   +---+---+---+---+ 
3 |   |   |   |   | 3 |   |   |   |   | 
  +---+---+---+---+   +---+---+---+---+ 
2 |   |   |   |   | 2 |   |   |   |   | 
  +---+---+---+---+   +---+---+---+---+ 
1 | 1 |   |   |   | 1 | 1 |   |   |   | 
  +---+---+---+---+   +---+---+---+---+ 
0 | 0 | 0 |   |   | 0 | 0 | 1 |   |   | 
  +---+---+---+---+   +---+---+---+---+ 
    0   1   2   3       0   1   2   3    

我们现在可以从(1, 0)开始,从(1, 0)开始,这等效于从(0, 1)开始,并且我们可以从(0, 1)开始。 如果我们从(1, 0)上升到(2, 0),则得到l_20 = l_10 + r_20 = 1 + 0 = 1v_20 = v_10 + g_20 = 1 + 0 = 1。如果我们从(1, 0)转到(1, 1),我们得到l_11 = l_10 + r_01 + r_11 = 1 + 1 + 1 = 3。请注意,这与从(0, 1)l_11 = l_01 + r_10 + r_11 = 1 + 1 + 1 = 3的上升完全相同。同样v_11 = v_10 + g_01 + g_11 = 1 + 1 = 2。最后,如果我们从(0, 1)转到(0, 2),我们将得到l_02 = l_01 + r_02 = 1 + 1 = 2v_02 = v_01 + g_02 = 0 + 3 = 3。这将导致以下模式:

          V                   L         
  +---+---+---+---+   +---+---+---+---+ 
3 |   |   |   |   | 3 |   |   |   |   | 
  +---+---+---+---+   +---+---+---+---+ 
2 | 1 |   |   |   | 2 | 1 |   |   |   | 
  +---+---+---+---+   +---+---+---+---+ 
1 | 1 | 2 |   |   | 1 | 1 | 3 |   |   | 
  +---+---+---+---+   +---+---+---+---+ 
0 | 0 | 0 | 3 |   | 0 | 0 | 1 | 2 |   | 
  +---+---+---+---+   +---+---+---+---+ 
    0   1   2   3       0   1   2   3    

我们现在可以从(2, 0)开始,从(2, 0)开始,这等效于从(1, 1)开始,从(1, 1)开始,等同于上升来自(0, 2),或者直接来自(0, 2)。如果我们从(2, 0)上升到(3, 0),则得到l_30 = l_20 + r_30 = 1 + 2 = 3v_30 = v_20 + g_30 = 1 + 1 = 2。 我们可以通过从(2, 1)向上计算(2, 0),但是从(1, 1)向上可以允许我们对预先计算的矩形的较大部分(4个单元格而不是3个单元格)进行相同的计算)。我们得到l_21 = l_11 + r_20 + r_21 = 3 + 0 + 1 = 4。由于超出限制,v_21 = 0。现在,任何包含(2, 1)的矩形都将具有与(2, 1)完全相同的单元格,包括那些加起来最多为4个红色部分的单元格。因此,所有包含(2, 1)的矩形都必须无效。这些都是带有x >= 2y >=1的单元格。为了明确起见,我们给它们l_xy = Inf。 单元格(1, 2)包含(0, 0),但是由于l_12 = l_11 + r_02 + r_12 = 3 + 1 + 0 = 4,因此该单元格无效,(1, 3)遵循与上述相同的逻辑。

最后,如果我们从(0, 2)转到(0, 3),我们将得到l_03 = l_02 + r_03 = 2 + 1 = 3v_03 = v_02 + g_03 = 3 + 1 = 4。这将导致以下模式:

          V                   L         
  +---+---+---+---+   +---+---+---+---+ 
3 | 2 | 0 | 0 | 0 | 3 | 3 |Inf|Inf|Inf| 
  +---+---+---+---+   +---+---+---+---+ 
2 | 1 | 0 | 0 | 0 | 2 | 1 |Inf|Inf|Inf| 
  +---+---+---+---+   +---+---+---+---+ 
1 | 1 | 2 | 0 | 0 | 1 | 1 | 3 |Inf|Inf| 
  +---+---+---+---+   +---+---+---+---+ 
0 | 0 | 0 | 3 | 4 | 0 | 0 | 1 | 2 | 3 | 
  +---+---+---+---+   +---+---+---+---+ 
    0   1   2   3       0   1   2   3    

因此,如您所见,具有最高值的矩形是由点(0, 3)组成的,值为4的矩形,我们在计算16个像元中只有10个时发现了这一点。当然,该算法的上限是O(MN),但实际上这是不可避免的,因为考虑没有红色碎片或限制很高的情况。然后,您仍然必须计算所有M * N个单元格的总和。

答案 1 :(得分:3)

由于角(0,0)是矩形的必需部分,因此整个任务非常简单。算法如下:

假设X,Y是木板的尺寸:

green_counts, red_counts = count_pieces()
found_pos = None
found_count = 0
for y in range(0, Y):
  x = find_x_for_y_with_max_red_pieces()
  g = green_counts(x, y)
  if g > found_count:
    found_count = g
    found_pos = x, y
print(found_pos)

首先,我们为矩形(0,0)->(x,y)创建带有红色和绿色计数的二维数组。然后我们迭代y。对于每个y,我们找到最大的x,满足红色的limis。我们计算绿色件数,并检查是否比以前更好。整个过程将在O(n^2)中运行,因为您需要计算件数。

请注意,您可以进一步提高内存需求(不需要完整的二维数组,只需要“上一个”行)。

问题2:如何处理浮动仓位?相同。对x个位置进行排序,然后将其替换为index。因此,例如对于(0,0),(0.2,1),(0.2,2.5),(0.4,1)点,您将获得(0,0),(1、1),(1、2),( 2,1)。然后使用上面的算法。

答案 2 :(得分:3)

O(n log n)解,其中n是个数

这是一种算法,它通过扫描片段而不是网格来工作。我认为它在O(p log p)中运行,其中p是件数,而不是O(grid_size ^ 2)。

这是一种双扫线算法。想象两条线,一条水平线定义矩形的顶部(代码中的top)和一条垂直线(x)定义右侧。顶线从y轴的网格顶部开始,而垂直线从y轴开始。垂直线向右掠过,当到达一块时采取动作。如果该部分位于顶行下方,则该部分将添加到当前矩形中。如果会有过多的红色碎片,则将水平线向下扫动,直到红色碎片的数量在限制范围内。水平线处或上方的所有绿色部分都将被删除,因为它们不在新的矩形中。向下移动矩形的顶部之前,请先检查绿色部分的数量,看看是否达到新的最大值。

适用于浮点坐标。

类似于python的range()排除上限的方式,矩形包括(0,0)但不包括函数返回的边界。例如,测试用例返回((4,4),5),这意味着由0 <= x <4和0 <= y <4定义的矩形有5个绿色小块(注意上限为“ <”)。对于整数坐标,矩形为(0,0)到(3,3)(含)。对于浮点坐标,上限不包括给定点。

import sortedcontainers as sc   # http://www.grantjenks.com/docs/sortedcontainers/

X,Y,Color = 0,1,2

def solve(pieces, size, max_reds):
    # Sort pieces by x, then red before green, then bottom-to-top
    pieces.sort(key=lambda t:(t[X],t[Color]=='g',t[Y]))

    # These keep track of the pieces that are in the rectangle. They
    # are sorted by 'y' value, so it is easy to remove pieces when 
    # 'top' is lowered due to too many reds in the rectangle.
    reds_in_rect = sc.SortedKeyList(key=lambda t:t[Y])
    greens_in_rect = sc.SortedKeyList(key=lambda t:t[Y])

    # For keeping track of the maximum number of green 
    # pieces and the corresponding coordinates.
    max_greens = 0
    max_x = 0
    max_y = 0

    # 'top' scans from top to bottom and represents the top of
    # the current rectangle.  It gets lowered to remove red pieces
    # from the rectangle when there are too many of them.   
    top = size[Y]

    # The pieces are sorted so this loop scans the pieces left-to-right.
    # If multiple pieces have the same x-coordinate, red ones come before
    # green ones, then lower ones before higher ones.
    for x,y,p in pieces:

        # Only pieces below the top of the rectangle are considered.
        # And they are added to the rectangle
        if y < top:

            if p == 'g':
                greens_in_rect.add((x,y,p))

            elif p == 'r':
                reds_in_rect.add((x,y,p))

                # If there are too many red pieces in the rectangle, 'top' gets
                # lowered to the 'y' value of the top-most red piece.  Then any
                # red or green pieces at or above the new 'top' get removed from
                # the rectangle.
                if len(reds_in_rect) > max_reds:

                    # before lowering the top of the rectangle check if current
                    # rectangle has a maximum number of green pieces
                    if len(greens_in_rect) > max_greens:
                        max_greens = len(greens_in_rect)
                        max_x = x
                        max_y = top

                    # lower to top to the 'y' value of the highest
                    # red piece seen so far
                    top = reds_in_rect[-1][Y]

                    # remove any red pieces at or above the top
                    # of the new rectangle
                    while reds_in_rect and reds_in_rect[-1][Y] >= top:
                        reds_in_rect.pop()

                    # remove any green pieces at or above the top
                    # of the new rectangle
                    while greens_in_rect and greens_in_rect[-1][Y] >= top:
                        greens_in_rect.pop()

    # last check of the number of green pieces
    if len(greens_in_rect) > max_greens:
        max_greens = len(greens_in_rect)
        max_x = size[X]
        max_y = top

    return (max_x, max_y), max_greens

测试案例:

#    +-+-+-+-+-+-+
#  3 | | | |o|x|o|
#  +-+-+-+-+-+-+
#  2 |o| |x| | |o|
#    +-+-+-+-+-+-+
#  1 |o| |o| |o|x|
#    +-+-+-+-+-+-+
#  0 | |o| |x| |x|
#    +-+-+-+-+-+-+
#     0 1 2 3 4 5

size = 6,4
max_reds = 2

red = [(3,0), (5,0), (5,1), (2,2), (4,3)]
green = [(1,0), (0,1), (2,1), (4,1), (0,2), (5,2), (3,3), (5,3)]

pieces = [(x,y,'r') for x,y in red] + [(x,y,'g') for x,y in green]

solve(pieces, size, max_reds)  # --> returns ((4,5),5)