You are given a stream of points consisting of x-y coordinates on a 2-D plane. Points can be added and queried as follows:
Implement the CountSquares class:
CountSquares() Initializes the object.void add(int[] point) Adds a new point point = [x, y].int count(int[] point) Counts the number of ways to form valid squares with point point = [x, y] as described above.Example 1:
Input:
["CountSquares", "add", [[1, 1]], "add", [[2, 2]], "add", [[1, 2]], "count", [[2, 1]], "count", [[3, 3]], "add", [[2, 2]], "count", [[2, 1]]]
Output:
[null, null, null, null, 1, 0, null, 2]
Explanation:
CountSquares countSquares = new CountSquares();
countSquares.add([1, 1]);
countSquares.add([2, 2]);
countSquares.add([1, 2]);
countSquares.count([2, 1]); // return 1.
countSquares.count([3, 3]); // return 0.
countSquares.add([2, 2]); // Duplicate points are allowed.
countSquares.count([2, 1]); // return 2. Constraints:
point.length == 20 <= x, y <= 1000
You should aim for a solution with O(1) time for each add() call, O(n) time for each count() call, and O(n) space, where n is the total number of points.
Initially, we can store the points in a global list for the add() call. For the count() call, a brute force approach would use three nested loops to check other points except for the query point, resulting in an O(n^3) time solution. Can you think of a better way? Maybe you should consider the observation that can be drawn from the diagonal of a square.
In a square's diagonal, the absolute difference between the x-coordinates is equal to the absolute difference between the y-coordinates of the two endpoints, and neither difference can be zero. Using these two points, we can determine the other diagonal endpoints.
We store points in a hash map instead of a list for O(1) lookups, treating duplicate points as one while tracking their frequencies. For the count() function, we iterate through points that, along with the query point, can form a diagonal. Let the query point be (qx, qy) and the other point be (x, y), ensuring they form a diagonal. What could be the other two points? Maybe you should consider the points forming a right-to-left diagonal, treating (qx, qy) as the top-right corner.
The other two points are point1 (x, qy) and point2 (qx, y). For counting, we simply add count of point1 * count of point2 to the result res.
Before attempting this problem, you should be comfortable with:
We are asked to count how many axis-aligned squares can be formed using a given point as one corner and previously added points as the other three corners.
Key observations about an axis-aligned square:
(px, py) is one corner and (x, y) is the diagonal opposite corner, then:|px - x| == |py - y| (equal side lengths)x != px and y != py (otherwise it would not form a square)(x, py)(px, y)So the idea is:
(px, py)(x, y) as a possible diagonalA hash map lets us quickly check how many times a specific point was added.
Data Structures
ptsCount: a hash map storing how many times each point appearspts: a list of all added points (including duplicates)add(point)
point in ptsCountpoint to the list ptscount(point)
res = 0(px, py) be the query point(x, y) in pts:(x, y) can be the diagonal opposite corner:|px - x| == |py - y|x != px and y != py(x, py) and (px, y)ptsCount[(x, py)] * ptsCount[(px, y)]resclass CountSquares:
def __init__(self):
self.ptsCount = defaultdict(int)
self.pts = []
def add(self, point: List[int]) -> None:
self.ptsCount[tuple(point)] += 1
self.pts.append(point)
def count(self, point: List[int]) -> int:
res = 0
px, py = point
for x, y in self.pts:
if (abs(py - y) != abs(px - x)) or x == px or y == py:
continue
res += self.ptsCount[(x, py)] * self.ptsCount[(px, y)]
return resWe want to count how many axis-aligned squares can be formed using the given query point (x1, y1) as one corner.
For an axis-aligned square:
(x1, y2) on the same vertical line (same x1), then:side = y2 - y1x3 = x1 + side (square to the right)x4 = x1 - side (square to the left)So for each possible vertical partner (x1, y2), we can form up to two squares:
(x3, y1) and (x3, y2)(x4, y1) and (x4, y2)Because points can be added multiple times, the total number of squares is the product of the counts of the required points.
This version uses a nested hash map:
ptsCount[x][y] = how many times point (x, y) was addedData Structure
ptsCount: nested hash mapadd(point)
(x, y).ptsCount[x][y].count(point)
res = 0.(x1, y1).y2 such that a point (x1, y2) exists (same x-coordinate):side = y2 - y1side == 0, skip (same point, no square)x3 = x1 + side (right square)x4 = x1 - side (left square)ptsCount[x1][y2] * ptsCount[x3][y1] * ptsCount[x3][y2]ptsCount[x1][y2] * ptsCount[x4][y1] * ptsCount[x4][y2]res.class CountSquares:
def __init__(self):
self.ptsCount = defaultdict(lambda: defaultdict(int))
def add(self, point: List[int]) -> None:
self.ptsCount[point[0]][point[1]] += 1
def count(self, point: List[int]) -> int:
res = 0
x1, y1 = point
for y2 in self.ptsCount[x1]:
side = y2 - y1
if side == 0:
continue
x3, x4 = x1 + side, x1 - side
res += (self.ptsCount[x1][y2] * self.ptsCount[x3][y1] *
self.ptsCount[x3][y2])
res += (self.ptsCount[x1][y2] * self.ptsCount[x4][y1] *
self.ptsCount[x4][y2])
return resA common mistake is only checking that the side lengths are equal (|px - x| == |py - y|) without also verifying that the points are not on the same horizontal or vertical line. Both conditions are necessary for a valid square diagonal.
# Wrong: Missing the x != px and y != py checks
if abs(py - y) == abs(px - x): # Accepts invalid diagonals
...
# Correct: Must also ensure points form a true diagonal
if abs(py - y) == abs(px - x) and x != px and y != py:
...The problem allows adding the same point multiple times. Using a set instead of a counter for point tracking will lose count information, causing incorrect results when the same point appears multiple times. The number of squares should multiply by the count of each required corner point.
When iterating through stored points to find potential squares, some solutions incorrectly look for adjacent corners instead of diagonal corners. Given the query point (px, py) and a candidate diagonal (x, y), the other two corners must be (x, py) and (px, y), not (px + side, py) and (px, py + side).