【问题标题】:Count nodes within k distance of marked nodes in grid计算网格中标记节点k距离内的节点
【发布时间】:2021-11-03 14:48:01
【问题描述】:

我正在尝试解决编码挑战,但我的解决方案性能不佳,我正在寻找有关如何改进算法的建议或建议。

谜题如下:

给定一个代表果园的单元格网格,每个单元格可以是空点 (0) 或果树 (1)。一位农民想知道果园内距离所有果树 k 距离内有多少空地。

使用taxicab geometry计算距离,例如:

k = 1

[1, 0]
[0, 0]

the answer is 2 as only the bottom right spot is >k distance from all trees.

我的解决方案是这样的:

  1. 遍历网格并存储所有树的位置
  2. 从第一个树位置开始 BFS 并存储所有空点,直到我们到达超过 k 距离的邻居
  3. BFS 从下一个树位置开始并存储空点的交集
  4. 重复第 3 步,直到我们遍历所有树位置
  5. 返回所有路口后剩余的空位数

我发现对于具有较大 k 值的大型网格,我的算法会变得非常慢,因为我最终会多次检查网格中的每个点。经过一番研究,我找到了一些类似问题的解决方案,建议取两个最极端的目标节点,然后只比较它们的距离:

但是,鉴于以下某些输入,这不适用于我的挑战:

k = 4

[0, 0, 0, 1]
[0, 1, 0, 0]
[0, 0, 0, 0]
[1, 0, 0, 0]
[0, 0, 0, 0]

使用极端节点方法,即使距离中间树 5 距离,右下角的空白点也会被计算在内。

谁能指出我更有效的方法?我对这些类型的问题还很陌生,所以我很难看到下一步应该采取的措施。

【问题讨论】:

  • "距离所有果树k距离内" 这看起来很奇怪。如果果园是 n × n 且 n > 2 * k,则所有树的 k 中都不能有一个点。
  • @ravenspoint 果园可能很大,但可能只有少数树,而且它们都彼此靠近。如果有两棵树的距离> 2 * k,则所有树的k内都没有点的情况;这不等于 n > 2 * k。
  • 您能否澄清一下您的意思是“所有树”还是“任何树”?
  • @Richard "all tr​​ees" 正确,看问题定义

标签: algorithm optimization graph-theory graph-algorithm


【解决方案1】:

这并不容易实现,但在许多情况下可能是次线性的,最多是线性的。考虑将每棵树的周长表示为四个角(它们标记一个旋转 45 度的正方形)。对于每棵树,计算它与当前交叉点的周边交叉点。困难在于管理交叉口的拐角,由于对角线对齐,交叉点可能包括多个点。跑到最后一个路口,数一数里面有多少空位。

【讨论】:

    【解决方案2】:

    由于您使用出租车距离,BFS 是不必要的。您可以直接计算空点和树之间的距离。

    该算法基于https://stackoverflow.com/users/3080723/stef的建议

    // select tree near top left corner
    SET flag false
    LOOP r over rows
      LOOP c over columns
         IF tree at c, r
             SET t to tree at c,r
             SET flag true
             BREAK
      IF flag
         BREAK   
    LOOP s over empty spots
      Calculate distance between s and t
      IF distance <= k
         ADD s to spotlist
    LOOP s over spotlist
      LOOP t over trees, starting at bottom right corner
         Calculate distance between s and t
         IF distance > k
             REMOVE s from spotlist
             BREAK
    RETURN spotlist
    

    【讨论】:

    • 这个解决方案与我开始使用的非常相似,但最后一个嵌套循环似乎真的会降低性能。我尝试了 BFS,因为我认为一旦到达邻居 >k 距离,我可以节省终止时间。
    • 糟糕,当从点列表中删除 s 时,我忘记了跳出树循环。这将对性能有很大帮助。
    • 第一次出现“计算 s 和 t 之间的距离”这一行时,不清楚 t 指的是什么;它似乎没有被声明。
    • @גלעדברקן 指的是SET t to tree at c,r
    【解决方案3】:

    由于网格和距离结构,这个问题有一个简单的线性时间解决方案。给定一棵坐标为 (a, b) 的果树,考虑 4 条对角线包围它周围距离为 k 的盒子。向下和向右的对角线具有恒定的 x + y 值,而向下和向左的对角线具有恒定的 x - y 值。

    点 (x, y) 在框内(因此,在 (a, b) 的距离 k 内)当且仅当:

    1. a + b - k
    2. a - b - k

    所以我们可以遍历我们的果树 (a, b) 以找到四个数字:

    • first_max = max(a + b - k); first_min = min(a + b + k);
    • second_max = max(a - b - k); second_min = min(a - b + k);

    其中 min 和 max 用于所有果树。然后,遍历空单元格(或者做一些数学运算并减去果树数量,如果您的网格很大),计算有多少空点 (x,y) 满足

    1. first_max
    2. second_max

    这段 Python 代码(以程序风格编写)说明了这个想法。每个边界框的每条对角线恰好截断了一半的平面,所以这相当于平行半平面的交集:

    fruit_trees = [(a, b) for a in range(len(grid))
                          for b in range(len(grid[0]))
                          if grid[a][b] == 1]
    
    northwest_half_plane = -infinity
    southeast_half_plane = infinity
    
    southwest_half_plane = -infinity
    northeast_half_plane = infinity
    
    for a, b in fruit_trees:
        northwest_half_plane = max(northwest_half_plane, a - b - k)
        southeast_half_plane = min(southeast_half_plane, a - b + k)
    
        southwest_half_plane = max(southwest_half_plane, a + b - k)
        northeast_half_plane = min(northeast_half_plane, a + b + k)
    
    count = 0
    for x in range(len(grid)):
        for y in range(len(grid[0])):
            if grid[x][y] == 0:
                if (northwest_half_plane <= x - y <= southeast_half_plane
                and southwest_half_plane <= x + y <= northeast_half_plane):
                    count += 1
    
    print(count)
    

    关于代码的一些注释:从技术上讲,数组坐标是从图片的笛卡尔坐标旋转四分之一圈,但这在这里无关紧要。代码故意遗漏了某些看起来很明显的“优化”,原因有两个:1. 最佳优化取决于果树和网格的输入格式,以及 2. 解决方案,虽然概念简单且简单阅读,在写作时要做到正确并不容易,重要的是代码“明显正确”。如果需要性能,可以稍后添加诸如“如果下限超过上限则提前退出并返回 0”之类的内容。

    【讨论】:

    • 这不是我在答案中发布的想法吗?没想到计算这么简单。
    • @גלעדברקן 从概念上讲,它是相关的,但实现可能看起来非常不同。您的答案也是线性时间,但是,它需要一个子程序来计算两个任意“对角轴对齐”矩形的角/周长的交集。这是解决一般二维凸相交问题的正确选择;然而,在第一次尝试时很难正确或简洁地编码。如果其他人认为我的回答是重复/衍生的,我很乐意将其删除。
    • 不,我认为你的回答很棒!
    • 但它不是有效地计算我想知道的交叉点吗?
    • @CaTs 我认为这需要我完成 45 度旋转的逻辑,但就像许多编程挑战一样,记住它的简单代数有时就足够了:如果曼哈顿距离 NE 象限的原点是x - 0 + y - 0 那么我们想要的是x - 0 + y - 0 &lt;= k。现在用a, b 代替原点,我们得到NE 象限的公式:x + y &lt;= k + a + b。我们可以从那里解决它。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-08-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-07-29
    • 1970-01-01
    • 2013-03-31
    相关资源
    最近更新 更多