Problem Walkthrough

Detect Squares LeetCode 2013 Guide

Master LeetCode 2013 Detect Squares by storing point counts in a HashMap keyed by (x, y) coordinates, then enumerating all possible diagonal corners of axis-aligned squares to compute the count product of all three non-query corners — handling duplicates correctly via multiplication.

9 min read|

Detect Squares

LeetCode 2013

Problem Overview — Design DetectSquares with Add and Count Operations

LeetCode 2013 Detect Squares asks you to design a data structure that supports two operations: add(point) which adds a point to the collection (duplicates allowed), and count(point) which returns the number of axis-aligned squares that can be formed using the query point as one corner and three points from the collection as the other three corners.

An axis-aligned square means all four sides are parallel to the x and y axes. Given a query point, you need to efficiently count how many valid squares exist — including counting each distinct combination of three stored points separately, even if multiple copies of those coordinates have been added via repeated add calls.

The problem has constraints that make brute-force infeasible for large inputs: up to 3000 add and count calls, with coordinates in the range [0, 1000]. A naive O(n^3) approach checking all triples of stored points per count call would be too slow. The key insight is that fixing one corner and one diagonal corner uniquely determines the other two corners.

  • point = [x, y] with 0 <= x, y <= 1000
  • Up to 3000 calls to add and count combined
  • Duplicate points are allowed — each copy counts separately
  • count returns the number of ways to form an axis-aligned square
  • The query point itself is not in the collection — it is a separate parameter
  • Squares must have positive, non-zero side lengths

What Makes an Axis-Aligned Square — Diagonal Corners Determine Everything

An axis-aligned square has four corners where opposite sides are parallel to the x-axis and y-axis. Given any two diagonally opposite corners (qx, qy) and (px, py), the square is fully determined: the side length equals abs(px - qx), which must equal abs(py - qy) for the corners to be diagonal on a square. The other two corners are (qx, py) and (px, qy).

This geometric fact is the entire algorithm. Rather than iterating over all possible squares or all triples of points, you fix the query point as one corner and enumerate all candidates for the diagonally opposite corner. For each valid diagonal candidate, the remaining two corners are calculated in O(1) — you just check if those corners exist in your data structure and multiply their counts.

The enumeration is efficient because you only need to check stored points that satisfy the diagonal constraint: abs(px - qx) == abs(py - qy) and px != qx. Every such point is a potential diagonal partner, and each one gives exactly one square (or zero, if the other two computed corners are missing from the collection).

💡

Fix the Query as One Corner — Enumerate Diagonals

Fix the query point (qx, qy) as one corner of the square. Enumerate all possible diagonal corners (px, py) from stored points where abs(px - qx) == abs(py - qy) and px != qx. The other two corners are always (qx, py) and (px, qy) — uniquely determined by the diagonal. This reduces the problem from O(n^3) triple enumeration to O(n) diagonal enumeration per count call.

HashMap Storage — Count Duplicates with a Point Frequency Map

Store all added points in a dictionary (or HashMap) keyed by (x, y) tuples, where the value is the count of how many times that point has been added. In Python this is naturally expressed as a defaultdict(int) or Counter. In Java, use a HashMap<String, Integer> or nested HashMap<Integer, HashMap<Integer, Integer>>.

The add operation is O(1): simply increment the count for the key (x, y). Because duplicates are allowed and each duplicate contributes independently to square counts, tracking the frequency is essential. A point added three times effectively exists as three separate points at that location, and the count operation must treat them as such when computing the number of valid squares.

The data structure also benefits from maintaining a separate set of all unique (x, y) coordinates added, or equivalently using the HashMap keys as the set of candidates to iterate over in count. This set grows at most with the number of distinct points added — at most 1001*1001 entries given the coordinate constraints — keeping iteration tractable.

  1. 1Initialize pointCount as defaultdict(int) mapping (x, y) -> frequency
  2. 2add(point): increment pointCount[(x, y)] by 1 — O(1)
  3. 3Store all unique point keys in a set or iterate HashMap.keySet() during count
  4. 4Duplicate points at the same coordinate increase frequency but not the key set
  5. 5Memory: at most 1001 * 1001 distinct keys in the worst case

Count Algorithm — Multiply Frequencies of All Three Non-Query Corners

For a query point (qx, qy), iterate over all stored points (px, py). For each point where abs(px - qx) == abs(py - qy) and px != qx (ensuring non-zero side length), treat (px, py) as the diagonally opposite corner. The other two corners are (qx, py) and (px, qy). Look up their counts in the frequency map — if either is zero, no square exists for this diagonal.

Multiply the counts of all three non-query corners: pointCount[(px, py)] * pointCount[(qx, py)] * pointCount[(px, qy)]. Add this product to the running total. The multiplication correctly handles duplicates: each copy of the diagonal and each copy of the two side corners independently contribute to valid squares, so the number of squares from this diagonal choice is exactly the product of the three frequencies.

The total count is the sum of these products over all valid diagonal points. Because we iterate over all stored points once per count call, the time complexity is O(n) per count where n is the number of distinct stored points. With at most 3000 add calls, n <= 3000 in the worst case, making each count call fast enough.

ℹ️

Product of Counts Handles Duplicates Correctly

If the diagonal corner (px, py) has been added 2 times, and the other two corners each have count 3, the number of valid squares using this diagonal is 2 * 3 * 3 = 18. Each of the 2 diagonal copies can pair with each of the 3 copies of corner A and each of the 3 copies of corner B, giving 18 independent squares. The query point itself is not in the collection so its count is implicitly 1 and need not be multiplied.

Optimizing the Enumeration — Iterate Same-Row Points to Skip Invalid Diagonals

A straightforward optimization reduces the constant factor: instead of iterating all stored points and checking the diagonal constraint, maintain a secondary mapping from x-coordinate to the set of y-values at that x. Then for count(qx, qy), iterate only over points sharing the same x-coordinate as a stored point — but a different x — to find potential same-row partners, then compute the implied diagonal.

Alternatively, maintain a mapping from y-coordinate to set of x-values. For each point (px, py) with the same y as the query (py == qy), compute the implied side length s = abs(px - qx). Check if (px, qy + s) and (qx, qy + s) exist, and also if (px, qy - s) and (qx, qy - s) exist. This approach enumerates candidates along the same horizontal row as the query, with two square directions (above and below).

Both approaches are O(n) per count in the worst case, but the row-based approach avoids iterating points that fail the diagonal constraint immediately. For inputs with many distinct x or y values, the savings are modest. The simpler all-points approach with the abs check is clear and correct; the row-based approach is a constant-factor optimization worth knowing for interview discussions.

  1. 1Maintain yToXs: a dict mapping y -> set of x-values at that y
  2. 2For count(qx, qy): iterate all px in yToXs[qy] where px != qx
  3. 3For each such px, side = abs(px - qx)
  4. 4Check squares above: need (px, qy + side) and (qx, qy + side)
  5. 5Check squares below: need (px, qy - side) and (qx, qy - side)
  6. 6Accumulate pointCount[(px, qy+side)] * pointCount[(qx, qy+side)] and pointCount[(px, qy-side)] * pointCount[(qx, qy-side)]

Code Walkthrough — Python and Java with defaultdict and Nested HashMap

Python solution: from collections import defaultdict; class DetectSquares: def __init__(self): self.ptCount = defaultdict(int); self.pts = set(); def add(self, point): self.ptCount[tuple(point)] += 1; self.pts.add(tuple(point)); def count(self, point): qx, qy = point; res = 0; for px, py in self.pts: if abs(px - qx) != abs(py - qy) or px == qx: continue; res += self.ptCount[(px, py)] * self.ptCount[(qx, py)] * self.ptCount[(px, qy)]; return res. The pts set avoids re-iterating duplicate coordinates during count.

Java solution: class DetectSquares { Map<String, Integer> ptCount = new HashMap<>(); Set<String> pts = new HashSet<>(); private String key(int x, int y) { return x + "," + y; } public void add(int[] p) { String k = key(p[0], p[1]); ptCount.merge(k, 1, Integer::sum); pts.add(k); } public int count(int[] point) { int qx = point[0], qy = point[1], res = 0; for (String pk : pts) { String[] parts = pk.split(","); int px = Integer.parseInt(parts[0]), py = Integer.parseInt(parts[1]); if (Math.abs(px-qx) != Math.abs(py-qy) || px == qx) continue; res += ptCount.get(pk) * ptCount.getOrDefault(key(qx,py),0) * ptCount.getOrDefault(key(px,qy),0); } return res; } }

Time complexity: add is O(1). count is O(n) where n is the number of distinct stored points. Space complexity: O(n) for the frequency map and point set. Both Python and Java solutions correctly handle all edge cases including duplicate points and queries with no valid squares.

⚠️

Skip Zero-Area Squares — Require px != qx

Always check that px != qx (and equivalently py != qy follows from the abs constraint). If px == qx, the "diagonal" is on the same vertical line as the query, which means side length would be zero — a degenerate point, not a square. Similarly if abs(px-qx) != abs(py-qy), the four points cannot form a square. These two checks together ensure only valid, non-degenerate, axis-aligned squares are counted.

Ready to master algorithm patterns?

YeetCode flashcards help you build pattern recognition through active recall and spaced repetition.

Start practicing now