检测正方形

标签: 设计 数组 哈希表 计数

难度: Medium

给你一个在 X-Y 平面上的点构成的数据流。设计一个满足下述要求的算法:

  • 添加 一个在数据流中的新点到某个数据结构中可以添加 重复 的点,并会视作不同的点进行处理。
  • 给你一个查询点,请你从数据结构中选出三个点,使这三个点和查询点一同构成一个 面积为正轴对齐正方形统计 满足该要求的方案数目

轴对齐正方形 是一个正方形,除四条边长度相同外,还满足每条边都与 x-轴 或 y-轴 平行或垂直。

实现 DetectSquares 类:

  • DetectSquares() 使用空数据结构初始化对象
  • void add(int[] point) 向数据结构添加一个新的点 point = [x, y]
  • int count(int[] point) 统计按上述方式与点 point = [x, y] 共同构造 轴对齐正方形 的方案数。

示例:

输入:
["DetectSquares", "add", "add", "add", "count", "count", "add", "count"]
[[], [[3, 10]], [[11, 2]], [[3, 2]], [[11, 10]], [[14, 8]], [[11, 2]], [[11, 10]]]
输出:
[null, null, null, null, 1, 0, null, 2]

解释:
DetectSquares detectSquares = new DetectSquares();
detectSquares.add([3, 10]);
detectSquares.add([11, 2]);
detectSquares.add([3, 2]);
detectSquares.count([11, 10]); // 返回 1 。你可以选择:
                               //   - 第一个,第二个,和第三个点
detectSquares.count([14, 8]);  // 返回 0 。查询点无法与数据结构中的这些点构成正方形。
detectSquares.add([11, 2]);    // 允许添加重复的点。
detectSquares.count([11, 10]); // 返回 2 。你可以选择:
                               //   - 第一个,第二个,和第三个点
                               //   - 第一个,第三个,和第四个点

提示:

  • point.length == 2
  • 0 <= x, y <= 1000
  • 调用 addcount总次数 最多为 5000

Submission

运行时间: 90 ms

内存: 17.8 MB

class DetectSquares:

    def __init__(self):
        self.x = defaultdict(dict)

    def add(self, point: List[int]) -> None:
        self.x[point[0]].setdefault(point[1], 0) 
        self.x[point[0]][point[1]] += 1

    def count(self, point: List[int]) -> int:
        x, y = point
        res = 0
        for p_y in self.x[x]:
            if p_y == y:
                continue
            length = abs(p_y - y)
            if x + length in self.x and y in self.x[x+length] and p_y in self.x[x+length]:
                res += self.x[x+length][y] * self.x[x+length][p_y] * self.x[x][p_y]
            if x - length in self.x and y in self.x[x-length] and p_y in self.x[x-length]:
                res += self.x[x-length][y] * self.x[x-length][p_y] * self.x[x][p_y]
        return res



# Your DetectSquares object will be instantiated and called as such:
# obj = DetectSquares()
# obj.add(point)
# param_2 = obj.count(point)

Explain

本题解通过使用一个嵌套的字典结构来存储点的坐标和频率。外层字典的键是点的x坐标,而内层字典的键是y坐标,内层字典的值则是该点的出现频率。添加点时,只需简单增加该点的计数。在计数函数中,遍历与查询点x坐标相同的所有点,计算与查询点y坐标的距离(即边长),然后检查是否存在与查询点形成正方形的其他三个点。这种方法通过直接访问字典的键来验证点的存在性,避免了遍历整个数据结构的需要。

时间复杂度: O(n) per query, where n is the number of points with the same x-coordinate as the query point

空间复杂度: O(N) where N is the number of unique points in the data structure

class DetectSquares:

    def __init__(self):
        self.x = defaultdict(dict)  # 初始化字典,存储点的x坐标和对应的y坐标及频率

    def add(self, point: List[int]) -> None:
        self.x[point[0]].setdefault(point[1], 0)  # 如果点的y坐标不在字典中,则初始化为0
        self.x[point[0]][point[1]] += 1  # 增加该点的出现次数

    def count(self, point: List[int]) -> int:
        x, y = point
        res = 0
        for p_y in self.x[x]:  # 遍历所有与查询点x坐标相同的点
            if p_y == y:
                continue  # 如果y坐标相同,则跳过
            length = abs(p_y - y)  # 计算边长
            if x + length in self.x and y in self.x[x+length] and p_y in self.x[x+length]:
                res += self.x[x+length][y] * self.x[x+length][p_y] * self.x[x][p_y]  # 如果存在正方形,则累加方案数
            if x - length in self.x and y in self.x[x-length] and p_y in self.x[x-length]:
                res += self.x[x-length][y] * self.x[x-length][p_y] * self.x[x][p_y]  # 同上
        return res  # 返回总方案数

Explore

在`count`函数中,目标是找到所有可能与查询点形成正方形的点的组合。因此,首先需要确定一条边的两个点,这两点的x坐标必须相同(即垂直边),这是因为正方形的边要么垂直要么水平。通过限定遍历所有与查询点x坐标相同的点,我们可以在这些点中查找可以与查询点形成垂直边的点。一旦确定了垂直边,正方形的剩余两个点位置也随之确定,这减少了需要遍历的点的数量,从而提高了效率。

在算法中,当考虑正方形的两种可能的位置(即正方形的一个边平行于x轴的两种对角线方向)时,必须检查形成正方形的其他两个点的坐标是否有效。这意味着在进行坐标计算后,我们需要验证这些计算出的坐标是否在有效范围内(例如在问题给定的坐标范围内,通常是0到1000)。在字典查找操作中,如果某个坐标不在字典中,则默认不存在该点,因此不会对不存在的点误认为存在。这种检查方式自然地处理了边界情况,因为任何超出边界的坐标都不会在字典中有对应的条目。

直接通过字典访问点的存在性是一种高效且通常安全的方法,因为字典的查找时间复杂度为O(1)。然而,这种方法可能在以下情况引发问题:1) 如果在访问字典时未先检查键是否存在,可能会引发KeyError错误。在本题解中,这通过安全地检查键是否存在来避免(例如使用`in`操作符)。2) 在极端情况下,如果点的分布极其稀疏,大多数坐标位置都没有点,那么在这些空坐标上的字典访问将变得不必要且效率低下,尤其是当尝试寻找不存在的点组合形成正方形时。此外,如果数据非常庞大,字典所占用的内存也可能成为问题。