2857. Count Pairs of Points With Distance k
Problem Description
You are given a 2D integer array coordinates
where each element coordinates[i] = [xi, yi]
represents the coordinates of the i-th point in a 2D plane. You are also given an integer k
.
The problem defines a special distance metric between two points. For any two points (x1, y1)
and (x2, y2)
, the distance is calculated as (x1 XOR x2) + (y1 XOR y2)
, where XOR is the bitwise XOR operation.
Your task is to count how many pairs of points (i, j)
satisfy the following conditions:
i < j
(to avoid counting the same pair twice and self-pairs)- The distance between point
i
and pointj
equals exactlyk
The solution uses a hash table to efficiently count valid pairs. For each point (x2, y2)
, it considers all possible ways to decompose k
into two parts a
and b
where a + b = k
. Since a
represents x1 XOR x2
and b
represents y1 XOR y2
, we can work backwards to find x1 = a XOR x2
and y1 = b XOR y2
. This works because XOR is its own inverse operation (if a = x1 XOR x2
, then x1 = a XOR x2
). The algorithm checks if point (x1, y1)
exists in the hash table and counts it. After processing each point, it's added to the hash table for future lookups, ensuring the i < j
constraint is maintained.
Intuition
The key insight comes from understanding the properties of the XOR operation. XOR has a special characteristic: it's its own inverse. This means if a XOR b = c
, then a XOR c = b
and b XOR c = a
. This property is crucial for our solution.
Given that we need to find pairs of points where (x1 XOR x2) + (y1 XOR y2) = k
, we can think about this problem differently. Instead of checking every possible pair of points (which would be O(n²)), we can be smarter.
For any point (x2, y2)
we're currently examining, we want to find all previous points (x1, y1)
that satisfy our distance condition. Since the distance must equal k
, and it's the sum of two XOR results, we can decompose k
into all possible sums: k = a + b
where a
is the result of x1 XOR x2
and b
is the result of y1 XOR y2
.
Since k
is bounded (typically small in this problem), we can enumerate all possible values of a
from 0
to k
, making b = k - a
. Now, using the inverse property of XOR, if we know a = x1 XOR x2
and we have x2
, we can find x1 = a XOR x2
. Similarly, y1 = b XOR y2
.
This transforms our problem from "find all pairs with distance k" to "for each point, calculate what other points would give distance k and check if they exist". We use a hash table to store points we've already seen, ensuring we only count pairs where i < j
by processing points in order and only looking backwards.
The elegance of this approach is that instead of comparing every pair (quadratic time), we do a constant amount of work for each point (since k
is bounded), making it much more efficient.
Solution Approach
The solution uses a hash table combined with enumeration to efficiently count valid pairs.
Data Structure:
- We use a
Counter
(hash table) calledcnt
to store the frequency of each point we've encountered so far. The key is a tuple(x, y)
representing the coordinates, and the value is the count of how many times this point appears.
Algorithm Steps:
-
Initialize variables:
- Create an empty
Counter
calledcnt
to track point occurrences - Set
ans = 0
to accumulate the count of valid pairs
- Create an empty
-
Process each point sequentially: For each point
(x2, y2)
incoordinates
:a. Enumerate all possible decompositions of k:
- Since we need
(x1 XOR x2) + (y1 XOR y2) = k
, we iteratea
from0
tok
- For each
a
, we calculateb = k - a
- Here,
a
represents the value ofx1 XOR x2
andb
representsy1 XOR y2
b. Calculate the required matching point:
- Using XOR's inverse property:
x1 = a XOR x2
- Similarly:
y1 = b XOR y2
- This gives us the exact coordinates
(x1, y1)
that would have distancek
from(x2, y2)
c. Count existing matches:
- Look up
cnt[(x1, y1)]
to see how many times this required point has appeared before - Add this count to
ans
d. Update the hash table:
- After checking all possible matches for the current point, add
(x2, y2)
tocnt
- This ensures we only count pairs where
i < j
(current point paired with previously seen points)
- Since we need
-
Return the result:
- Return
ans
which contains the total count of valid pairs
- Return
Time Complexity: O(n × k) where n is the number of points and k is the given distance value Space Complexity: O(n) for storing points in the hash table
The brilliance of this approach is that it avoids the naive O(n²) comparison of all pairs by leveraging the mathematical properties of XOR and the bounded nature of k
.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example to understand how the solution works.
Input: coordinates = [[1,2], [4,2], [1,3], [5,2]]
, k = 5
We need to find pairs of points where the XOR distance equals 5.
Step-by-step execution:
Initialize: cnt = {}
, ans = 0
Process Point 1: (1,2)
- For each decomposition of k=5 (a + b = 5):
- a=0, b=5: x1 = 0⊕1 = 1, y1 = 5⊕2 = 7 → Check (1,7): not in cnt
- a=1, b=4: x1 = 1⊕1 = 0, y1 = 4⊕2 = 6 → Check (0,6): not in cnt
- a=2, b=3: x1 = 2⊕1 = 3, y1 = 3⊕2 = 1 → Check (3,1): not in cnt
- a=3, b=2: x1 = 3⊕1 = 2, y1 = 2⊕2 = 0 → Check (2,0): not in cnt
- a=4, b=1: x1 = 4⊕1 = 5, y1 = 1⊕2 = 3 → Check (5,3): not in cnt
- a=5, b=0: x1 = 5⊕1 = 4, y1 = 0⊕2 = 2 → Check (4,2): not in cnt
- Add (1,2) to cnt:
cnt = {(1,2): 1}
ans = 0
Process Point 2: (4,2)
- For each decomposition of k=5:
- a=0, b=5: x1 = 0⊕4 = 4, y1 = 5⊕2 = 7 → Check (4,7): not in cnt
- a=1, b=4: x1 = 1⊕4 = 5, y1 = 4⊕2 = 6 → Check (5,6): not in cnt
- a=2, b=3: x1 = 2⊕4 = 6, y1 = 3⊕2 = 1 → Check (6,1): not in cnt
- a=3, b=2: x1 = 3⊕4 = 7, y1 = 2⊕2 = 0 → Check (7,0): not in cnt
- a=4, b=1: x1 = 4⊕4 = 0, y1 = 1⊕2 = 3 → Check (0,3): not in cnt
- a=5, b=0: x1 = 5⊕4 = 1, y1 = 0⊕2 = 2 → Check (1,2): Found! cnt[(1,2)] = 1
- Add (4,2) to cnt:
cnt = {(1,2): 1, (4,2): 1}
ans = 0 + 1 = 1
Verification: Distance between (1,2) and (4,2) = (1⊕4) + (2⊕2) = 5 + 0 = 5 ✓
Process Point 3: (1,3)
- For each decomposition of k=5:
- a=0, b=5: x1 = 0⊕1 = 1, y1 = 5⊕3 = 6 → Check (1,6): not in cnt
- a=1, b=4: x1 = 1⊕1 = 0, y1 = 4⊕3 = 7 → Check (0,7): not in cnt
- a=2, b=3: x1 = 2⊕1 = 3, y1 = 3⊕3 = 0 → Check (3,0): not in cnt
- a=3, b=2: x1 = 3⊕1 = 2, y1 = 2⊕3 = 1 → Check (2,1): not in cnt
- a=4, b=1: x1 = 4⊕1 = 5, y1 = 1⊕3 = 2 → Check (5,2): not in cnt
- a=5, b=0: x1 = 5⊕1 = 4, y1 = 0⊕3 = 3 → Check (4,3): not in cnt
- Add (1,3) to cnt:
cnt = {(1,2): 1, (4,2): 1, (1,3): 1}
ans = 1
Process Point 4: (5,2)
- For each decomposition of k=5:
- a=0, b=5: x1 = 0⊕5 = 5, y1 = 5⊕2 = 7 → Check (5,7): not in cnt
- a=1, b=4: x1 = 1⊕5 = 4, y1 = 4⊕2 = 6 → Check (4,6): not in cnt
- a=2, b=3: x1 = 2⊕5 = 7, y1 = 3⊕2 = 1 → Check (7,1): not in cnt
- a=3, b=2: x1 = 3⊕5 = 6, y1 = 2⊕2 = 0 → Check (6,0): not in cnt
- a=4, b=1: x1 = 4⊕5 = 1, y1 = 1⊕2 = 3 → Check (1,3): Found! cnt[(1,3)] = 1
- a=5, b=0: x1 = 5⊕5 = 0, y1 = 0⊕2 = 2 → Check (0,2): not in cnt
ans = 1 + 1 = 2
Verification: Distance between (1,3) and (5,2) = (1⊕5) + (3⊕2) = 4 + 1 = 5 ✓
Final Result: ans = 2
The algorithm found two valid pairs: [(1,2), (4,2)] and [(1,3), (5,2)], both with XOR distance equal to 5.
Solution Implementation
1class Solution:
2 def countPairs(self, coordinates: List[List[int]], k: int) -> int:
3 # Dictionary to store frequency of each coordinate seen so far
4 coordinate_count = Counter()
5 result = 0
6
7 # Process each coordinate as the second point in a potential pair
8 for x2, y2 in coordinates:
9 # Try all possible ways to split k into two parts (a and b)
10 # where a + b = k, to find all valid first points
11 for a in range(k + 1):
12 b = k - a
13
14 # Calculate the required first point coordinates
15 # Using XOR property: if x1 ^ x2 = a, then x1 = a ^ x2
16 # Similarly for y coordinates
17 x1 = a ^ x2
18 y1 = b ^ y2
19
20 # Add count of this coordinate if it exists in our map
21 result += coordinate_count[(x1, y1)]
22
23 # Add current coordinate to the map for future pairs
24 coordinate_count[(x2, y2)] += 1
25
26 return result
27
1class Solution {
2 public int countPairs(List<List<Integer>> coordinates, int k) {
3 // Map to store frequency of each coordinate pair
4 Map<List<Integer>, Integer> frequencyMap = new HashMap<>();
5 int pairCount = 0;
6
7 // Iterate through each coordinate in the list
8 for (List<Integer> currentCoordinate : coordinates) {
9 int currentX = currentCoordinate.get(0);
10 int currentY = currentCoordinate.get(1);
11
12 // For each possible split of k into two parts (a and b where a + b = k)
13 // We're looking for pairs where (x1 XOR x2) + (y1 XOR y2) = k
14 for (int xorValueForX = 0; xorValueForX <= k; xorValueForX++) {
15 int xorValueForY = k - xorValueForX;
16
17 // Calculate the required x1 and y1 values that would satisfy:
18 // x1 XOR currentX = xorValueForX and y1 XOR currentY = xorValueForY
19 int targetX = xorValueForX ^ currentX;
20 int targetY = xorValueForY ^ currentY;
21
22 // Check if this target coordinate exists in our map and add its frequency to answer
23 List<Integer> targetCoordinate = List.of(targetX, targetY);
24 pairCount += frequencyMap.getOrDefault(targetCoordinate, 0);
25 }
26
27 // Add current coordinate to the frequency map after checking for pairs
28 // This ensures we don't count a coordinate pairing with itself
29 frequencyMap.merge(currentCoordinate, 1, Integer::sum);
30 }
31
32 return pairCount;
33 }
34}
35
1class Solution {
2public:
3 int countPairs(vector<vector<int>>& coordinates, int k) {
4 // Map to store frequency of each coordinate point
5 // Key: (x, y) coordinate pair, Value: count of occurrences
6 map<pair<int, int>, int> coordinateFrequency;
7
8 // Total count of valid pairs
9 int pairCount = 0;
10
11 // Process each coordinate point
12 for (auto& currentCoordinate : coordinates) {
13 int currentX = currentCoordinate[0];
14 int currentY = currentCoordinate[1];
15
16 // For the current point (currentX, currentY), find all previous points
17 // (targetX, targetY) such that:
18 // (currentX XOR targetX) + (currentY XOR targetY) = k
19 //
20 // If we let xorX = currentX XOR targetX and xorY = currentY XOR targetY,
21 // then xorX + xorY = k, meaning xorY = k - xorX
22 //
23 // Given currentX and currentY, we can derive:
24 // targetX = xorX XOR currentX
25 // targetY = xorY XOR currentY
26
27 for (int xorX = 0; xorX <= k; ++xorX) {
28 int xorY = k - xorX;
29
30 // Calculate the target coordinates that would form a valid pair
31 int targetX = xorX ^ currentX;
32 int targetY = xorY ^ currentY;
33
34 // Add the count of previously seen target coordinates
35 pairCount += coordinateFrequency[{targetX, targetY}];
36 }
37
38 // Add current coordinate to the frequency map for future iterations
39 ++coordinateFrequency[{currentX, currentY}];
40 }
41
42 return pairCount;
43 }
44};
45
1/**
2 * Counts the number of pairs of coordinates (i, j) where i < j
3 * and (coordinates[i][0] XOR coordinates[j][0]) + (coordinates[i][1] XOR coordinates[j][1]) = k
4 *
5 * @param coordinates - Array of coordinate pairs [x, y]
6 * @param k - Target sum for XOR operations
7 * @returns Number of valid pairs
8 */
9function countPairs(coordinates: number[][], k: number): number {
10 // Map to store frequency of encoded coordinates
11 // Key: encoded coordinate value, Value: frequency count
12 const coordinateFrequency: Map<number, number> = new Map();
13
14 // Encodes a coordinate (x, y) into a single unique number
15 // Multiplying x by a large number ensures no collision for valid coordinate ranges
16 const encodeCoordinate = (x: number, y: number): number => {
17 return x * 1000000 + y;
18 };
19
20 let pairCount = 0;
21
22 // Iterate through each coordinate as the second coordinate in a pair
23 for (const [currentX, currentY] of coordinates) {
24 // Try all possible splits of k into two parts (a, b) where a + b = k
25 // We need: (x1 XOR currentX) = a and (y1 XOR currentY) = b
26 for (let xorPartX = 0; xorPartX <= k; ++xorPartX) {
27 const xorPartY = k - xorPartX;
28
29 // Calculate the required first coordinate values
30 // If x1 XOR currentX = xorPartX, then x1 = xorPartX XOR currentX
31 const requiredX = xorPartX ^ currentX;
32 const requiredY = xorPartY ^ currentY;
33
34 // Add count of previously seen coordinates that match our requirements
35 const encodedKey = encodeCoordinate(requiredX, requiredY);
36 pairCount += coordinateFrequency.get(encodedKey) ?? 0;
37 }
38
39 // Add current coordinate to the frequency map for future pairs
40 const currentEncodedKey = encodeCoordinate(currentX, currentY);
41 coordinateFrequency.set(
42 currentEncodedKey,
43 (coordinateFrequency.get(currentEncodedKey) ?? 0) + 1
44 );
45 }
46
47 return pairCount;
48}
49
Time and Space Complexity
Time Complexity: O(n × k)
The algorithm iterates through each coordinate pair in the coordinates
list (n iterations). For each coordinate (x2, y2)
, it performs an inner loop that runs k + 1
times (from 0 to k inclusive). Within the inner loop, the operations are:
- XOR operations to calculate
x1
andy1
:O(1)
- Dictionary lookup
cnt[(x1, y1)]
:O(1)
average case - Addition to
ans
:O(1)
After the inner loop, there's a dictionary update operation cnt[(x2, y2)] += 1
which is O(1)
average case.
Therefore, the total time complexity is O(n × (k + 1))
which simplifies to O(n × k)
.
Space Complexity: O(n)
The space is primarily used by the Counter
object cnt
, which stores coordinate pairs as keys. In the worst case, all n coordinate pairs are unique, so the dictionary will store n entries. Each entry consists of a tuple of two integers as the key and an integer count as the value, but this is still O(1)
space per entry.
Therefore, the total space complexity is O(n)
, where n is the length of the coordinates
array.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Order of Operations: Counting Before Adding to Hash Table
The Pitfall:
A common mistake is to add the current point to the hash table BEFORE checking for matching pairs. This would violate the i < j
constraint and potentially count self-pairs when duplicate points exist.
Incorrect Implementation:
def countPairs(self, coordinates: List[List[int]], k: int) -> int:
coordinate_count = Counter()
result = 0
for x2, y2 in coordinates:
# WRONG: Adding to counter first
coordinate_count[(x2, y2)] += 1
for a in range(k + 1):
b = k - a
x1 = a ^ x2
y1 = b ^ y2
result += coordinate_count[(x1, y1)]
return result
Why This Fails:
- If
k = 0
and we have a point(3, 5)
, the XOR distance from itself is 0 - Adding it to the counter first means we'd count the self-pair
((3,5), (3,5))
- With duplicate points, we'd count pairs in both directions, violating
i < j
Correct Approach: Always check for matches with previously seen points FIRST, then add the current point to the hash table.
2. Forgetting to Handle k = 0 Edge Case
The Pitfall:
When k = 0
, the only valid pairs are duplicate points (same coordinates). The XOR of identical values is always 0.
Example Scenario:
coordinates = [[1, 2], [1, 2], [3, 4]] k = 0 # Expected output: 1 (the pair of [1, 2] with itself)
The algorithm handles this correctly by design, but developers might try to "optimize" by skipping the loop when a = 0
and b = 0
, not realizing this is actually the case for finding duplicate points.
3. Using Regular Dictionary Instead of Counter
The Pitfall: Using a regular dictionary and forgetting to handle missing keys:
Incorrect Implementation:
coordinate_count = {} # Regular dict instead of Counter
result = 0
for x2, y2 in coordinates:
for a in range(k + 1):
b = k - a
x1 = a ^ x2
y1 = b ^ y2
# This will raise KeyError if (x1, y1) doesn't exist
result += coordinate_count[(x1, y1)]
# Also need to handle initialization
if (x2, y2) not in coordinate_count:
coordinate_count[(x2, y2)] = 0
coordinate_count[(x2, y2)] += 1
Solution:
Use Counter
from collections, which automatically returns 0 for missing keys and handles incrementing gracefully. Alternatively, use coordinate_count.get((x1, y1), 0)
with a regular dictionary.
How many ways can you arrange the three letters A, B and C?
Recommended Readings
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
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!