Determine whether points form squares
Company: Purestorage
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: medium
Interview Round: Onsite
# Determine whether points form squares
You are given points in the plane with integer coordinates. A square has four equal positive side lengths and four right angles. Its sides need not be parallel to the coordinate axes, and the input points are not ordered around the shape.
### Part 1 — Validate four points
Implement `validSquare(p1, p2, p3, p4) -> bool`, returning whether the four points are the vertices of a non-degenerate square. Each point is a two-element array `[x, y]`. Repeated coordinates cannot form a valid square.
The source exercise uses `-10000 <= x, y <= 10000`. The existing broader interview variant permits any signed 32-bit integer coordinates; explain how your arithmetic remains exact for that range. Its list-based interface, `is_square(points)`, receives the same four points in one list.
```text
p1 = [0,0], p2 = [1,1], p3 = [1,0], p4 = [0,1]
result = true
p1 = [0,0], p2 = [1,1], p3 = [1,0], p4 = [0,12]
result = false
p1 = [1,0], p2 = [-1,0], p3 = [0,1], p4 = [0,-1]
result = true
```
#### What This Part Should Cover
- Independence from input order and orientation.
- Rejection of coincident points, collinear points, and shapes that satisfy only some of the square conditions.
- Exact arithmetic, correctness, and the time and space cost for four points.
### Part 2 — Count squares among many points
Given `N` points, count the distinct geometric squares whose four vertices occur in the input. A square counts once regardless of vertex order. Repeated input coordinates do not create additional geometric squares or degenerate squares.
Implement `count_squares(points) -> int`. This interpretation matches the existing counting variant. The original interview prompt also asks you to clarify duplicate handling: discuss separately how the answer would change if distinct input occurrences at the same coordinate were considered different choices. Do not mix that alternative with the geometric count.
#### Constraints
- `1 <= N <= 20000` for the optimization discussion.
- Coordinates are signed 32-bit integers.
- Rotated squares count.
- You may assume constant average-time hash lookups when discussing the requested bounds.
```text
points = [[0,0], [1,0], [1,1], [0,1], [0,0]]
geometric square count = 1
```
If input occurrences were counted separately instead, the two occurrences of `[0,0]` would give two choices for that square.
Explain a baseline taking about `O(N^4)` time, an improvement taking about `O(N^3)`, and an approach taking about `O(N^2)`. These are algorithmic comparison targets; no wall-clock performance guarantee is specified for `N = 20000`.
#### What This Part Should Cover
- A counting rule that neither misses rotated squares nor counts the same geometric square repeatedly.
- A clear duplicate policy and its effect on the alternative occurrence count.
- The three requested time bounds, auxiliary storage, and arithmetic range.
### What a Strong Answer Covers
The answer solves both validation and counting, explains why every counted shape meets the square definition, and accounts for repeated discoveries and repeated input coordinates. It distinguishes the source's four-argument API from the broader list-based variants.
### Follow-up Questions
- How would you check the result when there are fewer than four distinct coordinates?
- Which intermediate quantities can exceed the coordinate type's range even though every input coordinate fits in that type?
Overview: This question evaluates computational geometry, combinatorial counting, and algorithmic optimization skills by requiring validation of a non-degenerate square from four 2D integer points and counting distinct squares in a larger point set, and it falls under the Coding & Algorithms category.
Validate a Single Square
You are given exactly 4 points on a 2D plane with integer coordinates as a list `points = [[x1,y1],[x2,y2],[x3,y3],[x4,y4]]`.
Determine whether these 4 points can be the vertices of a **non-degenerate square**. The square may be rotated (sides are not necessarily axis-aligned). A set of points where two or more coincide, or where the points are collinear, is **not** a valid square.
Return `true` if they form a square, otherwise `false`.
**Approach hint:** Compute all 6 pairwise squared distances. A square has exactly 4 equal smallest distances (the sides) and 2 equal largest distances (the diagonals), where diagonal² = 2 × side², and side² must be greater than 0. Using *squared* distances keeps everything in integer arithmetic and avoids floating-point error.
Constraints
- Exactly 4 points are provided.
- Coordinates fit in 32-bit signed integers.
- Coincident points or collinear points do not form a square.
- The square may be rotated (not necessarily axis-aligned).
Examples
Input: [[0,0],[0,1],[1,1],[1,0]]
Expected Output: True
Explanation: Unit axis-aligned square.
Input: [[0,0],[2,1],[3,-1],[1,-2]]
Expected Output: True
Explanation: Rotated square with side² = 5 and diagonal² = 10.
Hints
- Avoid floating point: compare *squared* distances so all math stays in integers.
- Collect all 6 pairwise squared distances and sort them.
- A valid square: the 4 smallest are equal (sides, > 0) and the 2 largest are equal and equal to 2× the side² (diagonals).
Count Squares in a Point Set
You are given `N` points on a 2D plane with integer coordinates as a list `points = [[x,y], ...]`. The input may contain duplicate coordinates; duplicates should be collapsed (they cannot help form a square).
Count how many **distinct squares** can be formed whose **4 vertices are all present in the set**. A square is the same regardless of the order of its vertices, and squares may be rotated.
**Optimization story (this is exactly the interview follow-up):**
- Brute force: try every 4-tuple of points and test if they form a square — about `O(n^4)`.
- Better: fix one side (a pair of points) and derive the other two vertices — about `O(n^3)`.
- Best (implemented here): iterate over every **pair of points treated as a diagonal**, derive the other two vertices via the perpendicular-diagonal formula, and check membership in a hash set — about `O(n^2)` with O(1) average lookups. Each square is discovered via both of its diagonals, so divide the final count by 2.
Given a diagonal with endpoints `(x1,y1)` and `(x2,y2)`, the other two vertices are `((x1+x2 + y1-y2)/2, (y1+y2 + x2-x1)/2)` and `((x1+x2 - y1+y2)/2, (y1+y2 - x2+x1)/2)`. They count only when those coordinates are integers and both are present in the set.
Constraints
- 1 <= N <= 2e4 for the optimized discussion.
- Coordinates fit in 32-bit signed integers.
- Duplicate coordinates are collapsed and cannot form a square.
- A square counts once regardless of vertex order; rotated squares count.
Examples
Input: [[0,0],[0,1],[1,1],[1,0]]
Expected Output: 1
Explanation: A single unit square.
Input: [[0,0],[0,1],[1,1],[1,0],[2,0],[2,1]]
Expected Output: 2
Explanation: A 2x3 grid of points forms two adjacent unit squares.
Hints
- Put all distinct points in a hash set for O(1) average membership tests.
- Treat each pair of points as a diagonal of a candidate square; the perpendicular bisector of equal length gives the other two vertices.
- Derived vertex coordinates may be non-integer (half values) — skip those (a real lattice square would land on integer coordinates).
- Each square has two diagonals, so it is counted twice — divide the total by 2.