【问题标题】:Finding the largest square in an n * n grid在 n * n 网格中找到最大的正方形
【发布时间】:2016-03-08 02:56:39
【问题描述】:

我有一个问题,我必须在 n * n 网格中找到最大的正方形。 例如

。 . . . . . # . # . . # . . . . # . # . . # . . .

其中最大的正方形是底角的 3 x 3。 我应该返回某人在右转之前可以采取的最多步骤,以便他们可以无限重复此操作而不会撞到墙“#”或超出 n * n 方格,这就是为什么输出比宽度/长度少一的原因广场。

我的代码从左到右、从上到下循环遍历网格,寻找朝下和朝右的顶点。一旦找到一个,它就会寻找最大可能的顶点朝上和向右,当它找到时检查所有四个边以查看它们是否组成或.。这段代码在 n = 100 附近的任何地方都可以在 1 秒内运行,但是我需要它在 n = 500 时在 1 秒内运行。关于如何加快速度的任何提示?

import sys
input = sys.stdin.readline

n = int(input())
maze = [list(input()) for _ in range(n)]

squares = 0
for r in range(n - 1):
    for c in range(n - 1):
        if maze[r][c] == '.' and maze[r][c + 1] == '.' and maze[r + 1]        [c] == '.':
            sides = []
            for i in range(min(n - r - 1, n - c - 1), -1, -1):
                if maze[r + i][c + i] == '.' and maze[r + i][c + i - 1] == '.' and maze[r + i - 1][c + i] == '.':
                    sides = i
                    if maze[r][c : c + sides] == ['.'] * sides and maze[r + sides][c : c + sides] == ['.'] * sides:
                        a = True
                        for j in range(sides):
                            if maze[r + j][c] != '.' or maze[r + j][c + sides] != '.':
                                a = False
                        if a and sides > squares:
                            squares = sides
                            break
            if squares == n - 1:
                break
print(squares)

【问题讨论】:

  • O(n^3) 算法怎么样?我认为它仍然可以适应 n = 500 的 1 秒
  • 为什么不是右上角的 3x3 正方形?
  • 两者都行,我必须编辑示例以使其格式正确,但没有看到。
  • @MSeifert 这就是它应该输出的内容。实际问题要求在向右转 90 度之前可以采取的最大步数。这与找到最大的平方只是输出减一是一样的。
  • 4x4 的正方形是什么样子的? 5x5 的正方形呢?

标签: python algorithm performance optimization


【解决方案1】:

我可以想到一个O(n^3)算法如下:

  1. 预计算 4 个数组:top[][], bottom[][], left[][], right[][],每个数组存储您可以从 (i,j) 开始的方向的最大长度
  2. 对于每个(i,j),将其用作正方形的左下角,对于其每个对角点(i-1, j+1), (i-2, j+2)...等,测试这些点是否可以用作正方形的右上角。存储过程中的最大平方边

对于第 1 步,所有 4 个数组都可以在 O(n^2) 中预先计算

对于第 2 步,当我们遍历所有 (i,j) 时,对于每个 (i,j),我们最多只能看到所有对角线点,其中最多 n,总共得到 O(n^3)

步骤 2 中的测试可以在O(1) 中使用 4 个预先计算的数组进行,只需检查“可能的正方形”的 4 个角是否可以通过检查相应的方向(上、下、左、右)来连接


当然,还有很多小事可以做加速,例如:

在步骤 2 中,对于每个 (i,j),只检查在 [current_maximum_diagonal_found ... max(right[i][j], top[i][j])] 范围内的对角点

沿着整个算法更新current_maximum_diagonal_found,这样我们就希望有一些(i,j),我们不需要检查整个n对角点。

但严格来说还是O(n^3),不过据我所知n~500应该能在1秒内跑完

【讨论】:

  • 好吧,我有同样的想法(见我的帖子),但实际上有两个预先计算的数组就足够了。例如,如果您从左上角开始,您只需要知道从每个点到右侧和底部可以走多远。
  • 是的,你完全正确,我只是粗略地提供了一个简单的想法,没有想太多......
【解决方案2】:

这是一个有趣的问题。我尝试了一些东西,最终得到了这个实现,它是O(n^3)。我对代码进行了注释,以便您可以满怀希望地遵循这个想法。速度仍有提升空间,但这个版本已经完成了这项工作(例如,迷宫大小为 500x500):

Finished after 0.708 seconds.
Result: 112581 squares found, maximum square (x=13, y=270, size=18).

这是源代码(Python 3):

import random
import pprint
import time

# small sample maze
maze = ['.....',
        '...#.',
        '.#...',
        '.#.#.',
        '.#...']
# convert to boolean maze
maze_bin = [[True if cell == '.' else False for cell in line] for line in maze]

# uncomment to generate a random maze
# maze_size = 500
# threshold = 0.2
# maze_bin = [[1 if random.random() >= threshold else 0 for _ in range(maze_size)] for _ in range(maze_size)]

# take start time
t1 = time.time()

# rotate the maze (first column becomes first row, first row becomes first column)
maze_bin_rot = [[maze_bin[i][j] for i in range(len(maze_bin))] for j in range(len(maze_bin[0]))]

# horizontal_lengths is a two-dimensional list that contains the number of possible steps to the right for every cell.
horizontal_lengths = []
for line in maze_bin:
    num = 0
    line_lengths = []
    for i in reversed(line):
        line_lengths.append(i*num)
        num = i * (num + i)
    horizontal_lengths.append(tuple(reversed(line_lengths)))

# vertical_lengths is a two-dimensional list that contains the number of possible steps to the bottom for every cell.
vertical_lengths_rot = []
for line in maze_bin_rot:
    num = 0
    line_lengths = []
    for i in reversed(line):
        line_lengths.append(i*num)
        num = i * (num + i)
    vertical_lengths_rot.append(tuple(reversed(line_lengths)))
# do the rotation again to be back in normal coordinates
vertical_lengths = [[vertical_lengths_rot[i][j] for i in range(len(vertical_lengths_rot))] for j in range(len(vertical_lengths_rot[0]))]

# calculate the maximum size of a square that has it's upper left corner at (x, y).
# this is the minimum of the possible steps to the right and to the bottom.
max_possible_square = []
for y in range(len(maze_bin)):
    line = []
    for x in range(len(maze_bin[0])):
        line.append(min(horizontal_lengths[y][x], vertical_lengths[y][x]))
    max_possible_square.append(line)

# search for squares
results = []
max_size_square = (-1, -1, -1)
for y in range(len(max_possible_square)):
    for x in range(len(max_possible_square[0])):
        # start with maximum possible size and decrease size until a square is found.
        for size in reversed(range(1, max_possible_square[y][x]+1)):
            # look at the upper right (x+size,y) and bottom left corner (x,y+size).
            # if it's possible to make at least size steps to the right from the bottom left corner
            # and at least size steps to the bottom from the upper right corner, this is a valid square.
            if horizontal_lengths[y+size][x] >= size and vertical_lengths[y][x+size] >= size:
                results.append((x, y, size+1))
                if size+1 > max_size_square[2]:
                    max_size_square = (x, y, size+1)
                # break after the the largest square with upper left corner (x,y) has been found.
                break

t2 = time.time()

# comment this print section if you use larger grids
print('Maze:')
pprint.pprint(maze_bin)
print('\n')
print('Horizontal possible steps:')
pprint.pprint(horizontal_lengths)
print('\n')
print('Vertical possible steps:')
pprint.pprint(vertical_lengths)
print('\n')
print('Maximum possible size of square:')
pprint.pprint(max_possible_square)
print('\n')
print('Results:')
for square in results:
    print('Square: x={}, y={}, size={}'.format(*square))
print('\n')

# final results
print('Finished after {:.3f} seconds.'.format(t2-t1))
print('Result: {} squares found, maximum square (x={}, y={}, size={}).'.format(len(results), *max_size_square))

我希望这就是您想要的。如果您有任何问题,请在下方留言;)

【讨论】:

    【解决方案3】:

    如果我们不想枚举所有结果,可能值得考虑的一种优化如下。它基于策略-“如果无法得出最佳解决方案,请不要继续使用此单元格”

    for y in range(possible_y_value):
        for x in range(possible_x_value):
            # We are ready to process cell identified by (x,y).
            # Check if max_possible_square_length at this cell is greater than size of best_result seen so far. If so, proceed further, otherwise skip this cell
            if max_possible_square[y][x]+1 > best_result.size:
                # proceed further with the inner most for loop
                ....
    

    即使在最里面的 for 循环中,我们也可以在迭代低于目前所见的 best_result 大小时跳出循环

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-02-22
      • 1970-01-01
      • 2012-01-06
      • 2023-03-22
      • 1970-01-01
      • 1970-01-01
      • 2021-11-03
      相关资源
      最近更新 更多