1128. Number of Equivalent Domino Pairs
Problem Description
You are given a list of dominoes where each domino is represented as a pair of integers [a, b]
. Two dominoes are considered equivalent if one can be rotated to match the other. Specifically, domino [a, b]
is equivalent to domino [c, d]
if:
- Either
a == c
andb == d
(they are exactly the same) - Or
a == d
andb == c
(one is the rotated version of the other)
For example, [1, 2]
and [2, 1]
are equivalent dominoes because rotating [2, 1]
gives us [1, 2]
.
Your task is to count the number of pairs (i, j)
where i < j
and dominoes[i]
is equivalent to dominoes[j]
.
The solution uses a clever counting approach. It normalizes each domino by creating a two-digit number where the smaller value is always placed first. For instance, both [1, 2]
and [2, 1]
would be converted to the number 12
(since 1 < 2
). Similarly, [3, 4]
and [4, 3]
would both become 34
.
As we iterate through the dominoes:
- For each domino, we create its normalized representation
x
- We add the current count of
x
to our answer (this represents all previous equivalent dominoes we can pair with) - We increment the count for
x
by 1 for future dominoes to pair with
This way, we efficiently count all pairs of equivalent dominoes in a single pass through the array.
Intuition
The key insight is recognizing that equivalent dominoes are essentially the same piece, just potentially rotated. When counting pairs, we need a way to identify which dominoes are equivalent regardless of their orientation.
Think about it this way: if you have a physical domino with numbers [1, 2]
, you can place it as either [1, 2]
or [2, 1]
by rotating it. They're the same domino! So we need a way to give both orientations the same "identity."
The natural approach is to create a canonical form for each domino - a standard way to represent it regardless of rotation. We can achieve this by always arranging the numbers in a consistent order, like smallest first. This way, both [1, 2]
and [2, 1]
become (1, 2)
in our canonical form.
Once we have this canonical representation, counting pairs becomes straightforward. As we process each domino:
- We check how many dominoes with the same canonical form we've seen before
- Each of those previously seen dominoes can form a pair with the current one
- We then record that we've seen one more domino of this type
The formula a * 10 + b
(where a < b
) is just a clever way to create a unique identifier for each canonical form. Since domino values are single digits (0-9), multiplying the first by 10 and adding the second creates a unique two-digit number. For example, [1, 2]
becomes 12
, and [3, 4]
becomes 34
. This makes it easy to use a counter or array to track occurrences.
The beauty of this approach is that we only need one pass through the array, making it efficient with O(n)
time complexity.
Solution Approach
The solution uses a counting approach with hash map to efficiently track equivalent dominoes.
Data Structure: We use a Counter
(hash map) to store the frequency of each normalized domino representation.
Algorithm Steps:
-
Initialize variables:
cnt
: A Counter to track occurrences of each normalized dominoans
: Running total of equivalent pairs, initially 0
-
Process each domino: For each domino
[a, b]
in the input:a. Normalize the domino: Create a canonical representation by ensuring the smaller number comes first:
x = a * 10 + b if a < b else b * 10 + a
This converts the domino into a unique two-digit number. For example:
[1, 2]
→12
[2, 1]
→12
(same as above)[3, 4]
→34
b. Count pairs with previous dominoes:
ans += cnt[x]
If we've seen
cnt[x]
dominoes with the same normalized form before, the current domino can formcnt[x]
pairs with them.c. Update the counter:
cnt[x] += 1
Record that we've now seen one more domino with this normalized form.
-
Return the result: After processing all dominoes,
ans
contains the total number of equivalent pairs.
Example Walkthrough:
Consider dominoes = [[1,2], [2,1], [3,4], [1,2]]
- Process
[1,2]
: x = 12, ans += 0 (no previous 12s), cnt[12] = 1 - Process
[2,1]
: x = 12, ans += 1 (one previous 12), cnt[12] = 2 - Process
[3,4]
: x = 34, ans += 0 (no previous 34s), cnt[34] = 1 - Process
[1,2]
: x = 12, ans += 2 (two previous 12s), cnt[12] = 3
Final answer: 3 pairs
Time Complexity: O(n)
where n is the number of dominoes
Space Complexity: O(n)
for the counter in worst case when all dominoes are unique
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 the solution with dominoes = [[1,2], [3,4], [2,1], [1,2], [4,3]]
Initial State:
cnt = {}
(empty counter)ans = 0
(no pairs found yet)
Step 1: Process [1,2]
- Normalize: Since 1 < 2, we get
x = 1*10 + 2 = 12
- Add pairs:
ans += cnt[12] = 0
(no previous dominoes with value 12) - Update counter:
cnt[12] = 1
- State:
cnt = {12: 1}
,ans = 0
Step 2: Process [3,4]
- Normalize: Since 3 < 4, we get
x = 3*10 + 4 = 34
- Add pairs:
ans += cnt[34] = 0
(no previous dominoes with value 34) - Update counter:
cnt[34] = 1
- State:
cnt = {12: 1, 34: 1}
,ans = 0
Step 3: Process [2,1]
- Normalize: Since 2 > 1, we get
x = 1*10 + 2 = 12
(same as [1,2]!) - Add pairs:
ans += cnt[12] = 1
(found 1 previous domino to pair with) - Update counter:
cnt[12] = 2
- State:
cnt = {12: 2, 34: 1}
,ans = 1
Step 4: Process [1,2]
- Normalize: Since 1 < 2, we get
x = 1*10 + 2 = 12
- Add pairs:
ans += cnt[12] = 2
(found 2 previous dominoes to pair with) - Update counter:
cnt[12] = 3
- State:
cnt = {12: 3, 34: 1}
,ans = 3
Step 5: Process [4,3]
- Normalize: Since 4 > 3, we get
x = 3*10 + 4 = 34
(same as [3,4]!) - Add pairs:
ans += cnt[34] = 1
(found 1 previous domino to pair with) - Update counter:
cnt[34] = 2
- State:
cnt = {12: 3, 34: 2}
,ans = 4
Final Answer: 4 pairs
The pairs are:
- (0,2): [1,2] and [2,1]
- (0,3): [1,2] and [1,2]
- (1,4): [3,4] and [4,3]
- (2,3): [2,1] and [1,2]
Solution Implementation
1from typing import List
2from collections import Counter
3
4class Solution:
5 def numEquivDominoPairs(self, dominoes: List[List[int]]) -> int:
6 # Counter to track frequency of normalized dominoes
7 domino_count = Counter()
8
9 # Total number of equivalent pairs found
10 total_pairs = 0
11
12 # Process each domino
13 for first_val, second_val in dominoes:
14 # Normalize the domino by ensuring smaller value comes first
15 # This treats [a,b] and [b,a] as equivalent
16 normalized_key = first_val * 10 + second_val if first_val < second_val else second_val * 10 + first_val
17
18 # Add count of previously seen equivalent dominoes to total
19 # (forms pairs with current domino)
20 total_pairs += domino_count[normalized_key]
21
22 # Increment count for this normalized domino
23 domino_count[normalized_key] += 1
24
25 return total_pairs
26
1class Solution {
2 public int numEquivDominoPairs(int[][] dominoes) {
3 // Array to count occurrences of each normalized domino
4 // Index represents the normalized value (0-99)
5 int[] count = new int[100];
6
7 // Total number of equivalent pairs
8 int result = 0;
9
10 // Iterate through each domino
11 for (int[] domino : dominoes) {
12 // Normalize the domino by placing smaller value first
13 // This ensures [a,b] and [b,a] map to the same value
14 int normalizedValue;
15 if (domino[0] < domino[1]) {
16 normalizedValue = domino[0] * 10 + domino[1];
17 } else {
18 normalizedValue = domino[1] * 10 + domino[0];
19 }
20
21 // Add the count of previously seen equivalent dominoes to result
22 // Then increment the count for this normalized value
23 result += count[normalizedValue];
24 count[normalizedValue]++;
25 }
26
27 return result;
28 }
29}
30
1class Solution {
2public:
3 int numEquivDominoPairs(vector<vector<int>>& dominoes) {
4 // Array to count occurrences of each normalized domino
5 // Index represents the normalized value (smaller*10 + larger)
6 int count[100] = {};
7
8 // Total number of equivalent domino pairs
9 int result = 0;
10
11 // Iterate through each domino
12 for (auto& domino : dominoes) {
13 // Normalize the domino by placing smaller value first
14 // This ensures [a,b] and [b,a] map to the same value
15 int normalizedValue = (domino[0] < domino[1]) ?
16 domino[0] * 10 + domino[1] :
17 domino[1] * 10 + domino[0];
18
19 // Add the current count to result (pairs with previous identical dominoes)
20 // Then increment the count for this normalized value
21 result += count[normalizedValue]++;
22 }
23
24 return result;
25 }
26};
27
1/**
2 * Counts the number of equivalent domino pairs in the given array.
3 * Two dominoes are equivalent if [a, b] is equivalent to [c, d] when either:
4 * - a == c and b == d, or
5 * - a == d and b == c
6 *
7 * @param dominoes - Array of domino pairs, where each domino is represented as [a, b]
8 * @returns The number of equivalent domino pairs
9 */
10function numEquivDominoPairs(dominoes: number[][]): number {
11 // Array to count occurrences of each normalized domino
12 // Index represents the normalized key (smaller * 10 + larger)
13 const dominoCount: number[] = new Array(100).fill(0);
14
15 // Total count of equivalent pairs
16 let pairCount: number = 0;
17
18 // Process each domino
19 for (const [firstValue, secondValue] of dominoes) {
20 // Normalize the domino by creating a key where smaller value comes first
21 // This ensures [1,2] and [2,1] map to the same key (12)
22 const normalizedKey: number = firstValue < secondValue
23 ? firstValue * 10 + secondValue
24 : secondValue * 10 + firstValue;
25
26 // Add the count of previously seen equivalent dominoes to the result
27 // This forms pairs with the current domino
28 pairCount += dominoCount[normalizedKey];
29
30 // Increment the count for this normalized domino
31 dominoCount[normalizedKey]++;
32 }
33
34 return pairCount;
35}
36
Time and Space Complexity
The time complexity is O(n)
, where n
is the number of dominoes. The algorithm iterates through the dominoes list exactly once, and for each domino, it performs constant-time operations: creating a normalized key x
, accessing and updating the counter, and adding to the answer.
The space complexity is O(C)
, where C
represents the maximum number of distinct normalized domino representations possible. Since each domino [a, b]
is normalized to a two-digit number (with the smaller value as tens digit and larger value as units digit), and both a
and b
range from 0 to 9, there are at most 10 × 10 = 100
possible distinct keys in the counter. Therefore, C = 100
, making the space complexity O(100)
or simply O(1)
in terms of constant space relative to the input constraints.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Normalization Logic
A common mistake is incorrectly normalizing the domino representation, which can lead to collisions or incorrect equivalence detection.
Pitfall Example:
# Wrong: Simple addition doesn't uniquely identify dominoes
normalized_key = first_val + second_val # [1,3] and [2,2] both give 4!
# Wrong: Concatenation without proper multiplication
normalized_key = str(min(first_val, second_val)) + str(max(first_val, second_val))
# This creates strings, not numbers, affecting performance and storage
Solution: Always use a multiplication factor that ensures unique representation:
normalized_key = min(first_val, second_val) * 10 + max(first_val, second_val)
2. Overflow Risk with Large Values
If domino values can be larger than single digits, the multiplication by 10 won't work correctly.
Pitfall Example:
# If dominoes can have values like [12, 5] normalized_key = 5 * 10 + 12 # Gives 62 # But [6, 2] also gives 62!
Solution: For larger values, use tuples as keys or a larger multiplication factor:
# Option 1: Use tuples (more general)
normalized_key = (min(first_val, second_val), max(first_val, second_val))
# Option 2: Use appropriate multiplication factor
max_val = 100 # If values go up to 99
normalized_key = min(first_val, second_val) * max_val + max(first_val, second_val)
3. Not Handling the Counting Logic Correctly
Some might try to count all pairs at once after collecting frequencies, leading to incorrect results or double counting.
Pitfall Example:
# Wrong approach: Counting pairs after collecting all frequencies for domino in dominoes: normalized_key = ... domino_count[normalized_key] += 1 # Then trying to calculate pairs for count in domino_count.values(): total_pairs += count * (count - 1) // 2 # This works but less efficient
Solution: The incremental counting approach in the original solution is both elegant and efficient - it counts pairs as we go, avoiding the need for a second pass.
4. Forgetting Edge Cases
Not considering special cases like dominoes with equal values.
Pitfall Example:
# Forgetting that [2,2] should be treated the same way
if first_val != second_val: # Wrong: unnecessary special case
normalized_key = min(first_val, second_val) * 10 + max(first_val, second_val)
else:
normalized_key = first_val * 10 + second_val
Solution: The normalization logic should work uniformly for all cases:
# This works for both [2,2] and [1,2] cases
normalized_key = min(first_val, second_val) * 10 + max(first_val, second_val)
Depth first search is equivalent to which of the tree traversal order?
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!