2013. Detect Squares
On LeetCode ->Problem¶
Maintain a multiset of 2D points:
add(point)stores one occurrence.count(point)returns how many triples of stored occurrences form a positive-area, axis-aligned square with the query point.
Duplicates count separately; the query point need not be stored.
class DetectSquares:
def __init__(self):
pass
def add(self, point: list[int]) -> None:
pass
def count(self, point: list[int]) -> int:
pass
Key trick¶
Choose a stored point on the query's horizontal line; its distance fixes the square's size, leaving just two possible pairs of corners. Multiply the three stored corners' frequencies.
Trap¶
Exclude zero-width squares, check both vertical directions, and preserve duplicate multiplicities without multiplying by the query point's frequency.
Why is it interesting?¶
Geometry reduces a three-point search to frequency lookups, while duplicates turn counting shapes into counting combinations.
Python solution¶
class DetectSquares:
def __init__(self):
# Map each y-coordinate to {x-coordinate: frequency}.
self.rows = {}
def add(self, point: list[int]) -> None:
x, y = point
row = self.rows.setdefault(y, {})
row[x] = row.get(x, 0) + 1
def count(self, point: list[int]) -> int:
x, y = point
ans = 0
for x1, freq in self.rows.get(y, {}).items():
if x1 == x:
continue # Positive area requires distinct horizontal corners.
width = abs(x1 - x)
for y1 in (y - width, y + width):
row = self.rows.get(y1, {})
# Each occurrence of each stored corner is an independent choice.
ans += freq * row.get(x, 0) * row.get(x1, 0)
return ans
Expected time: add takes \(O(1)\); count takes \(O(k)\), where \(k\) is the number of distinct stored points on the query's horizontal line.
Space: \(O(u)\), where \(u\) is the number of distinct stored points.
Comment on my solution¶
Your solution is correct: repeated entries in horizontal[y] implicitly account for the horizontal corner's multiplicity, while the two Counter lookups account for the other corners.
Its query time is \(O(p_y)\), where \(p_y\) includes duplicates on that row, and storage is \(O(a)\) for \(a\) additions. Storing horizontal frequencies instead avoids repeated work and reduces storage to \(O(u)\).
from collections import Counter
# WORKS
class DetectSquares:
def __init__(self):
self.freqs = Counter()
self.horizontal = {}
def add(self, point: list[int]) -> None:
x, y = point
self.freqs[(x,y)] += 1
self.horizontal.setdefault(y, []).append(x)
def count(self, point: list[int]) -> int:
x, y = point
cnt = 0
for x1 in self.horizontal.get(y, []):
if x1 == x:
continue
width = abs(x - x1)
for y1 in (y + width, y - width):
cnt += self.freqs[(x, y1)] * self.freqs[(x1, y1)]
return cnt