447. Number of Boomerangs
Problem Description
You are given n
distinct points on a 2D plane, where each point is represented as points[i] = [xi, yi]
.
A boomerang is defined as a tuple of three points (i, j, k)
where:
- The distance from point
i
to pointj
equals the distance from pointi
to pointk
- The order of the tuple matters (meaning
(i, j, k)
is different from(i, k, j)
)
Your task is to count the total number of boomerangs that can be formed from the given points.
For example, if you have points A, B, and C where A is equidistant from both B and C, then you can form two boomerangs: (A, B, C)
and (A, C, B)
. Point A acts as the "pivot" point, while B and C are the two points at equal distance from A.
The key insight is that for each point that serves as the pivot (point i
), you need to find all pairs of other points that are equidistant from it. If there are x
points at the same distance from a pivot point, you can form x * (x - 1)
boomerangs with that pivot, since you can choose any of the x
points as j
and any of the remaining x - 1
points as k
.
Intuition
To solve this problem, we need to think about what makes a boomerang. A boomerang (i, j, k)
requires point i
to be equidistant from points j
and k
. This means point i
acts as the "center" or "pivot" of the boomerang.
The key realization is that we can fix each point as the pivot point i
and then look for all other points that share the same distance from it. If multiple points are at the same distance from our pivot, we can form multiple boomerangs.
For instance, if we have a pivot point i
and there are 3 points (A, B, C) all at distance d
from i
, we can form the following boomerangs:
(i, A, B)
,(i, A, C)
(i, B, A)
,(i, B, C)
(i, C, A)
,(i, C, B)
That's 3 × 2 = 6
boomerangs total. In general, if there are x
points at the same distance from pivot i
, we can choose any one as point j
(x choices) and any other as point k
(x-1 choices), giving us x × (x - 1)
boomerangs.
The approach becomes clear: for each point as a potential pivot, we:
- Calculate distances from this pivot to all other points
- Group points by their distance from the pivot
- For each group with
x
points at the same distance, addx × (x - 1)
to our total count
We can optimize this further by using a counting technique. As we iterate through points for a fixed pivot, we can maintain a counter of distances seen so far. When we encounter a distance d
that we've seen cnt[d]
times before, we know we can form cnt[d] × 2
new boomerangs (the current point paired with each previous point at distance d
, considering both orders).
Solution Approach
The solution implements an enumeration and counting strategy to efficiently count all boomerangs.
Algorithm Steps:
-
Initialize the answer counter to 0, which will store the total number of boomerangs.
-
Enumerate each point as the pivot - For each point
p1
in the points array, treat it as the pivot pointi
of potential boomerangs. -
For each pivot, create a distance counter - Initialize an empty hash table
cnt
using Python'sCounter()
to track the frequency of distances from the current pivot to other points. -
Calculate distances and count incrementally - For each other point
p2
:- Calculate the distance
d
fromp1
top2
using thedist()
function - Before updating the counter, add
cnt[d]
to the answer. This represents the number of boomerangs we can form with the current pointp2
and all previously seen points at the same distanced
- Increment
cnt[d]
by 1 to record this new point at distanced
- Calculate the distance
-
Double the final result - Since each boomerang
(i, j, k)
is counted once when processing it as(i, j, k)
and once as(i, k, j)
in our incremental counting approach, but we only count one direction, we multiply the final answer by 2 using bit shiftans << 1
.
Why this incremental counting works:
When we encounter a point at distance d
and we've already seen cnt[d]
points at this distance, we can form cnt[d]
new boomerang pairs with this new point (pairing it with each of the previous points). Since order matters, each pair contributes 2 boomerangs, but our incremental approach counts each pair once from each direction, so we need to double the final result.
Time Complexity: O(n²)
where n
is the number of points, as we iterate through all pairs of points.
Space Complexity: O(n)
for the hash table storing distance counts for each pivot point.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example with 4 points to illustrate the solution approach:
Given points: [[0,0], [1,0], [2,0], [1,1]]
Let's label them as A(0,0), B(1,0), C(2,0), and D(1,1) for clarity.
Step 1: Use A(0,0) as pivot
- Initialize
cnt = {}
and process each other point: - Distance to B(1,0):
d = 1² + 0² = 1
cnt[1] = 0
initially, so add 0 to answer- Update
cnt[1] = 1
- Distance to C(2,0):
d = 2² + 0² = 4
cnt[4] = 0
initially, so add 0 to answer- Update
cnt[4] = 1
- Distance to D(1,1):
d = 1² + 1² = 2
cnt[2] = 0
initially, so add 0 to answer- Update
cnt[2] = 1
- Running total: 0
Step 2: Use B(1,0) as pivot
- Initialize
cnt = {}
and process each other point: - Distance to A(0,0):
d = 1² + 0² = 1
cnt[1] = 0
initially, so add 0 to answer- Update
cnt[1] = 1
- Distance to C(2,0):
d = 1² + 0² = 1
cnt[1] = 1
now, so add 1 to answer (can form boomerang with A and C)- Update
cnt[1] = 2
- Distance to D(1,1):
d = 0² + 1² = 1
cnt[1] = 2
now, so add 2 to answer (can form boomerangs with A&D and C&D)- Update
cnt[1] = 3
- Running total: 0 + 1 + 2 = 3
Step 3: Use C(2,0) as pivot
- Initialize
cnt = {}
and process each other point: - Distance to A(0,0):
d = 2² + 0² = 4
cnt[4] = 0
initially, so add 0 to answer- Update
cnt[4] = 1
- Distance to B(1,0):
d = 1² + 0² = 1
cnt[1] = 0
initially, so add 0 to answer- Update
cnt[1] = 1
- Distance to D(1,1):
d = 1² + 1² = 2
cnt[2] = 0
initially, so add 0 to answer- Update
cnt[2] = 1
- Running total: 3 + 0 = 3
Step 4: Use D(1,1) as pivot
- Initialize
cnt = {}
and process each other point: - Distance to A(0,0):
d = 1² + 1² = 2
cnt[2] = 0
initially, so add 0 to answer- Update
cnt[2] = 1
- Distance to B(1,0):
d = 0² + 1² = 1
cnt[1] = 0
initially, so add 0 to answer- Update
cnt[1] = 1
- Distance to C(2,0):
d = 1² + 1² = 2
cnt[2] = 1
now, so add 1 to answer (can form boomerang with A and C)- Update
cnt[2] = 2
- Running total: 3 + 1 = 4
Step 5: Double the result
- Final answer: 4 × 2 = 8 boomerangs
The 8 boomerangs are:
- With B as pivot and A,C equidistant: (B,A,C) and (B,C,A)
- With B as pivot and A,D equidistant: (B,A,D) and (B,D,A)
- With B as pivot and C,D equidistant: (B,C,D) and (B,D,C)
- With D as pivot and A,C equidistant: (D,A,C) and (D,C,A)
Solution Implementation
1from typing import List
2from collections import Counter
3
4class Solution:
5 def numberOfBoomerangs(self, points: List[List[int]]) -> int:
6 """
7 Count the number of boomerang tuples (i, j, k) where distance(i, j) == distance(i, k).
8
9 Args:
10 points: List of 2D points represented as [x, y] coordinates
11
12 Returns:
13 Total number of valid boomerang tuples
14 """
15 total_boomerangs = 0
16
17 # For each point as the center point (i in the tuple)
18 for center_point in points:
19 # Track count of points at each distance from the center
20 distance_count = Counter()
21
22 # Calculate distances from center to all other points
23 for other_point in points:
24 # Calculate squared Euclidean distance (avoid sqrt for efficiency)
25 distance = (center_point[0] - other_point[0]) ** 2 + \
26 (center_point[1] - other_point[1]) ** 2
27
28 # For each point at this distance, it can form boomerangs with
29 # all previously seen points at the same distance
30 total_boomerangs += distance_count[distance]
31
32 # Increment count for this distance
33 distance_count[distance] += 1
34
35 # Multiply by 2 because each valid pair (j, k) can be ordered in 2 ways
36 return total_boomerangs * 2
37
1class Solution {
2 public int numberOfBoomerangs(int[][] points) {
3 int totalBoomerangs = 0;
4
5 // For each point, treat it as the middle point of potential boomerangs
6 for (int[] centerPoint : points) {
7 // Map to store distance -> count of points at that distance
8 Map<Integer, Integer> distanceCount = new HashMap<>();
9
10 // Calculate distance from center point to all other points
11 for (int[] targetPoint : points) {
12 // Calculate squared Euclidean distance (no need for square root since we only compare)
13 int squaredDistance = (centerPoint[0] - targetPoint[0]) * (centerPoint[0] - targetPoint[0]) +
14 (centerPoint[1] - targetPoint[1]) * (centerPoint[1] - targetPoint[1]);
15
16 // If we've seen this distance before, we can form new boomerangs
17 // with the current point and all previous points at the same distance
18 totalBoomerangs += distanceCount.getOrDefault(squaredDistance, 0);
19
20 // Update the count for this distance
21 distanceCount.merge(squaredDistance, 1, Integer::sum);
22 }
23 }
24
25 // Multiply by 2 because for each valid triplet (i, j, k),
26 // we can also form (i, k, j) as another valid boomerang
27 return totalBoomerangs << 1;
28 }
29}
30
1class Solution {
2public:
3 int numberOfBoomerangs(vector<vector<int>>& points) {
4 int totalBoomerangs = 0;
5
6 // For each point as the middle point of boomerang
7 for (auto& centerPoint : points) {
8 // Map to store distance -> count of points at that distance
9 unordered_map<int, int> distanceCount;
10
11 // Calculate distances from current center point to all other points
12 for (auto& targetPoint : points) {
13 // Calculate squared distance (avoiding sqrt for efficiency)
14 int squaredDistance = (centerPoint[0] - targetPoint[0]) * (centerPoint[0] - targetPoint[0]) +
15 (centerPoint[1] - targetPoint[1]) * (centerPoint[1] - targetPoint[1]);
16
17 // If we already have n points at this distance, we can form n new boomerangs
18 // with the current point (each existing point can pair with the new one)
19 totalBoomerangs += distanceCount[squaredDistance];
20
21 // Increment count for this distance
22 distanceCount[squaredDistance]++;
23 }
24 }
25
26 // Multiply by 2 because each boomerang (i, j, k) can also be (i, k, j)
27 return totalBoomerangs << 1;
28 }
29};
30
1/**
2 * Counts the number of boomerang tuples (i, j, k) where the distance
3 * from point i to j equals the distance from point i to k.
4 *
5 * @param points - Array of 2D points where each point is [x, y]
6 * @returns The total number of boomerang tuples
7 */
8function numberOfBoomerangs(points: number[][]): number {
9 let totalBoomerangs = 0;
10
11 // For each point as the center point of potential boomerangs
12 for (const [centerX, centerY] of points) {
13 // Map to store frequency of distances from the center point
14 const distanceFrequencyMap: Map<number, number> = new Map();
15
16 // Calculate distances from center point to all other points
17 for (const [targetX, targetY] of points) {
18 // Calculate squared Euclidean distance (avoid sqrt for efficiency)
19 const squaredDistance = (centerX - targetX) ** 2 + (centerY - targetY) ** 2;
20
21 // If we've seen this distance before, we can form boomerangs
22 // with all previous points at the same distance
23 const currentFrequency = distanceFrequencyMap.get(squaredDistance) || 0;
24 totalBoomerangs += currentFrequency;
25
26 // Update the frequency count for this distance
27 distanceFrequencyMap.set(squaredDistance, currentFrequency + 1);
28 }
29 }
30
31 // Multiply by 2 because each boomerang (i, j, k) can also be (i, k, j)
32 return totalBoomerangs << 1;
33}
34
Time and Space Complexity
The time complexity is O(n^2)
, where n
is the length of the array points
. This is because we have two nested loops - the outer loop iterates through all points as p1
, and for each p1
, the inner loop iterates through all points as p2
. This gives us n × n = n^2
iterations in total.
The space complexity is O(n)
, where n
is the length of the array points
. The space is primarily used by the Counter
object cnt
, which in the worst case stores distances from one point p1
to all other points. Since there are at most n
unique distances from any single point to all other points (including itself), the Counter can hold at most n
entries at any given time. The Counter is reset for each new p1
in the outer loop, so we only need O(n)
space rather than O(n^2)
.
Common Pitfalls
1. Using Floating-Point Distance Calculations
The Pitfall:
A common mistake is calculating the actual Euclidean distance using sqrt()
, which introduces floating-point precision issues. When comparing distances, floating-point errors can cause two equal distances to be treated as different values in the hash table.
# INCORRECT - Floating point precision issues import math distance = math.sqrt((p1[0] - p2[0])**2 + (p1[1] - p2[1])**2)
The Solution: Use squared distances instead. Since we only need to check if distances are equal (not their actual values), comparing squared distances works perfectly and avoids floating-point arithmetic entirely:
# CORRECT - Use squared distance distance = (p1[0] - p2[0])**2 + (p1[1] - p2[1])**2
2. Incorrect Counting Formula
The Pitfall:
Some might try to count all boomerangs at once after collecting all distances, using the formula n * (n-1)
directly:
# INCORRECT - This misses the incremental nature for dist, count in distance_count.items(): if count >= 2: total_boomerangs += count * (count - 1)
The Solution:
The incremental counting approach shown in the solution is correct. When we encounter each new point at distance d
, we add distance_count[d]
(the number of previously seen points at this distance) to our total. This naturally accounts for all pairs.
3. Including the Center Point in Distance Calculations
The Pitfall: Forgetting that the center point will have distance 0 to itself, which shouldn't form valid boomerangs:
# This includes distance from point to itself (always 0) for other_point in points: distance = calculate_distance(center_point, other_point) # Distance 0 when center_point == other_point
The Solution: While the code still calculates distance to itself (which is 0), this doesn't create incorrect boomerangs because:
- A single point at distance 0 won't form any boomerangs (need at least 2 points at the same distance)
- The incremental counting naturally handles this case correctly
Alternatively, you could explicitly skip the center point:
for other_point in points: if other_point == center_point: continue distance = calculate_distance(center_point, other_point)
4. Forgetting to Multiply by 2
The Pitfall:
The incremental counting approach counts each valid pair once, but since order matters in boomerangs (i, j, k)
vs (i, k, j)
, forgetting the final multiplication by 2 will give half the correct answer:
# INCORRECT - Missing the multiplication return total_boomerangs # Should be total_boomerangs * 2
The Solution: Always remember to multiply the final result by 2, as each pair of equidistant points forms 2 different boomerangs based on their ordering.
Which of the following is a min heap?
Recommended Readings
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!