Facebook Pixel

2857. Count Pairs of Points With Distance k

MediumBit ManipulationArrayHash Table
Leetcode Link

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 point j equals exactly k

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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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) called cnt 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:

  1. Initialize variables:

    • Create an empty Counter called cnt to track point occurrences
    • Set ans = 0 to accumulate the count of valid pairs
  2. Process each point sequentially: For each point (x2, y2) in coordinates:

    a. Enumerate all possible decompositions of k:

    • Since we need (x1 XOR x2) + (y1 XOR y2) = k, we iterate a from 0 to k
    • For each a, we calculate b = k - a
    • Here, a represents the value of x1 XOR x2 and b represents y1 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 distance k 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) to cnt
    • This ensures we only count pairs where i < j (current point paired with previously seen points)
  3. Return the result:

    • Return ans which contains the total count of valid pairs

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 Evaluator

Example 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 and y1: 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.

Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

How many ways can you arrange the three letters A, B and C?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More