You are given a stream of points on the X-Y plane. Design an algorithm that:
An axis-aligned square is a square whose edges are all the same length and are either parallel or perpendicular to the x-axis and y-axis.
Implement the DetectSquares class:
DetectSquares() Initializes the object with an empty data structure.void add(int[] point) Adds a new point point = [x, y] to the data structure.int count(int[] point) Counts the number of ways to form axis-aligned squares with point point = [x, y] as one of the vertices.Input:
["DetectSquares", "add", "add", "add", "count", "count", "add", "count"]
[[], [[3, 10]], [[11, 2]], [[3, 2]], [[11, 10]], [[14, 8]], [[11, 2]], [[11, 10]]]
Output:
[null, null, null, null, 1, 0, null, 2]
Explanation:
DetectSquares detectSquares = new DetectSquares();
detectSquares.add([3, 10]);
detectSquares.add([11, 2]);
detectSquares.add([3, 2]);
detectSquares.count([11, 10]); // return 1
// The square is formed by points [11,10], [3,10], [3,2], [11,2]
detectSquares.count([14, 8]); // return 0
// No square can be formed
detectSquares.add([11, 2]); // Add duplicate point
detectSquares.count([11, 10]); // return 2
// Now we have 2 ways: one using each [11,2]
point.length == 20 <= x, y <= 10003000 calls in total will be made to add and countThis is a design problem that tests hash table usage and geometric understanding.
Axis-aligned square property: If two points (x1, y1) and (x3, y3) are diagonal corners of an axis-aligned square:
|x3 - x1| = |y3 - y1|(x1, y3) and (x3, y1)Count duplicates: We need to track frequency of each point, not just existence
Efficient lookup: Use hash map for O(1) point lookups
Data Structure:
Map<String, Integer> pointCount
// Key: "x,y", Value: frequency of that point
add(point):
count(point):
(x1, y1)(x3, y3):
|x3 - x1| ≠ |y3 - y1| (not a square)(x1, y3) and (x3, y1)count(x1,y3) × count(x3,y1) × count(x3,y3) to resultIf we have:
(x1, y3)(x3, y1)(x3, y3)Then we can form 2 × 3 × 1 = 6 different squares using these points!
For count([11, 10]) with points [3,10], [11,2], [3,2]:
(3,10) -------- (11,10) [query]
| |
| |
| |
(3,2) -------- (11,2)
Diagonal: (11,10) to (3,2)
Other corners: (3,10) and (11,2)
Side length: |11-3| = |10-2| = 8 ✓
All four corners exist → count = 1
add(): O(1)
count(): O(n)
Space: O(n)
import java.util.*;
class DetectSquares {
private Map<String, Integer> pointCount;
private List<int[]> points;
public DetectSquares() {
pointCount = new HashMap<>();
points = new ArrayList<>();
}
public void add(int[] point) {
String key = point[0] + "," + point[1];
pointCount.put(key, pointCount.getOrDefault(key, 0) + 1);
points.add(point);
}
public int count(int[] point) {
int x1 = point[0];
int y1 = point[1];
int totalCount = 0;
// Try each stored point as diagonal corner
for (int[] p : points) {
int x3 = p[0];
int y3 = p[1];
// Skip if not diagonal (same row or column)
if (x3 == x1 || y3 == y1) {
continue;
}
// Check if it forms a square (equal side lengths)
if (Math.abs(x3 - x1) != Math.abs(y3 - y1)) {
continue;
}
// The other two corners
String corner1 = x1 + "," + y3;
String corner2 = x3 + "," + y1;
String corner3 = x3 + "," + y3;
// Count combinations
int count1 = pointCount.getOrDefault(corner1, 0);
int count2 = pointCount.getOrDefault(corner2, 0);
int count3 = pointCount.getOrDefault(corner3, 0);
totalCount += count1 * count2 * count3;
}
return totalCount;
}
}
from collections import defaultdict
from typing import List
class DetectSquares:
def __init__(self):
self.point_count = defaultdict(int)
self.points = []
def add(self, point: List[int]) -> None:
x, y = point
self.point_count[(x, y)] += 1
self.points.append(point)
def count(self, point: List[int]) -> int:
x1, y1 = point
total_count = 0
# Try each stored point as diagonal corner
for x3, y3 in self.points:
# Skip if not diagonal
if x3 == x1 or y3 == y1:
continue
# Check if square (equal side lengths)
if abs(x3 - x1) != abs(y3 - y1):
continue
# Other two corners
count1 = self.point_count[(x1, y3)]
count2 = self.point_count[(x3, y1)]
count3 = self.point_count[(x3, y3)]
total_count += count1 * count2 * count3
return total_count
Instead of iterating all points (including duplicates), iterate only unique points:
class DetectSquares {
private Map<String, Integer> pointCount;
public DetectSquares() {
pointCount = new HashMap<>();
}
public void add(int[] point) {
String key = point[0] + "," + point[1];
pointCount.put(key, pointCount.getOrDefault(key, 0) + 1);
}
public int count(int[] point) {
int x1 = point[0];
int y1 = point[1];
int totalCount = 0;
// Iterate unique points only
for (String key : pointCount.keySet()) {
String[] parts = key.split(",");
int x3 = Integer.parseInt(parts[0]);
int y3 = Integer.parseInt(parts[1]);
if (x3 == x1 || y3 == y1) continue;
if (Math.abs(x3 - x1) != Math.abs(y3 - y1)) continue;
int count1 = pointCount.getOrDefault(x1 + "," + y3, 0);
int count2 = pointCount.getOrDefault(x3 + "," + y1, 0);
int count3 = pointCount.getOrDefault(key, 0);
totalCount += count1 * count2 * count3;
}
return totalCount;
}
}
This avoids counting the same diagonal multiple times!
This problem demonstrates:
|dx| = |dy| for square validationQ: What if we want to detect rectangles instead of squares?
|dx| = |dy| constraintQ: What if squares don't need to be axis-aligned?
Q: How to optimize if we have millions of points?
Q: What if we need to delete points?
Tests understanding of:
This is a practical problem for spatial data structures and geometric queries!
Square Detection:
(x1, y1) and (x3, y3)|x3 - x1| = |y3 - y1|(x1, y3) and (x3, y1)Count Formula:
total = Σ count(x1,y3) × count(x3,y1) × count(x3,y3)
for all valid diagonal points (x3,y3)
| Operation | Approach | Time | Space | |-----------|----------|------|-------| | add() | Hash Map | O(1) | O(n) | | count() | Iterate all points | O(n) | - | | count() | Iterate unique points | O(unique) | - |
Recommendation: Use unique points iteration for better average-case performance.